Skip to content

Release v<2.3.0> #487

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [2.3.0]
### Added

- Utilitário `convert_code_to_uf` [#397](https://github.com/brazilian-utils/brutils-python/pull/410)
Expand All @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Utilitário `get_municipality_by_code` [412](https://github.com/brazilian-utils/brutils-python/pull/412)
- Utilitário `get_code_by_municipality_name` [#399](https://github.com/brazilian-utils/brutils-python/issues/399)
- Utilitário `format_currency` [#426](https://github.com/brazilian-utils/brutils-python/issues/426)
- Utilitário `is_valid_renavam` [#430](https://github.com/brazilian-utils/brutils-python/issues/430)
- Utilitário `is_valid_rg` [#428] (https://github.com/brazilian-utils/brutils-python/issues/428)

## [2.2.0] - 2024-09-12

Expand Down
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1254,6 +1254,40 @@ Exemplo:
None
```

## RENAVAM

### is_valid_renavam

Valida o número de registro de veículo brasileiro (RENAVAM).

Esta função recebe uma string RENAVAM e verifica se é válida. Um RENAVAM
válido consiste exatamente em 11 dígitos. O último dígito é um dígito verificador
calculado a partir dos primeiros 10 dígitos usando um sistema de pesos específicos.

Parâmetros:
renavam (str): A string do RENAVAM a ser validada.

Retorna:
bool: True se o RENAVAM for válido, False caso contrário.

Exemplo:

```python
>>> from brutils import is_valid_renavam
>>> is_valid_renavam('35298206229')
True
>>> is_valid_renavam('12345678900')
False
>>> is_valid_renavam('1234567890a')
False
>>> is_valid_renavam('12345678 901')
False
>>> is_valid_renavam('12345678') # Less than 11 digits
False
>>> is_valid_renavam('') # Empty string
False
```

# Novos Utilitários e Reportar Bugs

Caso queira sugerir novas funcionalidades ou reportar bugs, basta criar
Expand Down
34 changes: 34 additions & 0 deletions README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -1257,6 +1257,40 @@ Example:
None
```

## RENAVAM

### is_valid_renavam

Validates the Brazilian vehicle registration number (RENAVAM).

This function takes a RENAVAM string and checks if it is valid.
A valid RENAVAM consists of exactly 11 digits. Theast digit a check digit
calculated from the the first 10 digits using a specific weighting system.

Args:
renavam (str): The RENAVAM string to be validated.

Returns:
bool: True if the RENAVAM is valid, False otherwise.

Example:

```python
>>> from brutils import is_valid_renavam
>>> is_valid_renavam('35298206229')
True
>>> is_valid_renavam('12345678900')
False
>>> is_valid_renavam('1234567890a')
False
>>> is_valid_renavam('12345678 901')
False
>>> is_valid_renavam('12345678') # Less than 11 digits
False
>>> is_valid_renavam('') # Empty string
False
```

# Feature Request and Bug Report

If you want to suggest new features or report bugs, simply create
Expand Down
10 changes: 10 additions & 0 deletions brutils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@
from brutils.pis import is_valid as is_valid_pis
from brutils.pis import remove_symbols as remove_symbols_pis

# RENAVAM Imports
from brutils.renavam import is_valid_renavam

# RG Imports
from brutils.rg import is_valid_rg

# Voter ID Imports
from brutils.voter_id import format_voter_id
from brutils.voter_id import generate as generate_voter_id
Expand Down Expand Up @@ -121,6 +127,10 @@
"generate_pis",
"is_valid_pis",
"remove_symbols_pis",
# RENAVAM
"is_valid_renavam",
# RG
"is_valid_rg",
# Voter ID
"format_voter_id",
"generate_voter_id",
Expand Down
48 changes: 48 additions & 0 deletions brutils/renavam.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
def is_valid_renavam(renavam): # type: (str) -> bool
"""
Validates the Brazilian vehicle registration number (RENAVAM).

This function takes a RENAVAM string and checks if it is valid.
A valid RENAVAM consists of exactly 11 digits. Theast digit a check digit
calculated from the the first 10 digits using a specific weighting system.

Args:
renavam (str): The RENAVAM string to be validated.

Returns:
bool: True if the RENAVAM is valid, False otherwise.

Example:
>>> is_valid_renavam('35298206229')
True
>>> is_valid_renavam('12345678900')
False
>>> is_valid_renavam('1234567890a')
False
>>> is_valid_renavam('12345678 901')
False
>>> is_valid_renavam('12345678') # Less than 11 digits
False
>>> is_valid_renavam('') # Empty string
False
"""

if len(renavam) != 11 or not renavam.isdigit():
return False

## Calculating the check digit
digits = [int(digit) for digit in renavam[:10]] # 10 digits
weights = [3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
checksum = sum(
digit * weight for digit, weight in zip(digits, weights)
) # Sum of the products of the digits and weights

remainder = checksum % 11
check_digit = 0 if remainder == 0 else 11 - remainder

# If the calculated check digit is 0, return False
if check_digit == 0:
return False

# Checking if the calculated check digit is equal to the last digit of the RENAVAM
return int(renavam[-1]) == check_digit
116 changes: 116 additions & 0 deletions brutils/rg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import re


def is_valid_rg(rg: str, uf: str) -> bool:
"""
Validates a Brazilian RG (Registro Geral) based on the state (UF).

This function checks whether a given RG is valid for a specific state in Brazil.
Each state may have its own RG format, and the function should handle these differences.

Additionally, for RGs issued by various states, the function verifies the mathematical rule
for the check digit where applicable.

Args:
rg (str): The RG to be validated.
uf (str): The state (UF) for which the RG should be validated.

Returns:
bool: Returns True if the RG is valid, or False if it is invalid.

Example:
>>> is_valid_rg('12.345.678-9', 'SP')
True
>>> is_valid_rg('MG-12.345.678', 'MG')
True
>>> is_valid_rg('123456789', 'RJ')
False
>>> is_valid_rg('A12345678', 'SP')
False
>>> is_valid_rg('12.345.678', 'SP')
False
"""
# Se rg ou uf forem None, retorna False
if rg is None or uf is None:
return False

# Remove caracteres indesejados (mantém dígitos, letras, pontos e hífens)
rg_cleaned = re.sub(r"[^0-9A-Za-z.-]", "", rg)

# Definição de padrões comuns para RG
common_patterns = {
"9_digits": r"^\d{9}$", # Ex.: '123456789'
"8_digits_dash": r"^\d{8}-\d$", # Ex.: '12345678-9'
"7_10_digits": r"^\d{1,2}\.\d{3}\.\d{3}$", # Ex.: '1.234.567' ou '12.345.678'
"3_groups": r"^\d{3}\.\d{3}\.\d{3}$", # Ex.: '123.456.789'
"3_groups_dash": r"^\d{2}\.\d{3}\.\d{3}-[0-9X]$", # Ex.: '12.345.678-9' ou com 'X' no dígito verificador
"2_groups_dash": r"^\d{2}\.\d{3}\.\d{3}$", # Ex.: '12.345.678'
"2_3_groups_dash": r"^\d{2}\.\d{3}\.\d{3}-\d$", # Ex.: '12.345.678-9'
"mg_format": r"^MG-\d{2}\.\d{3}\.\d{3}$", # Ex.: 'MG-12.345.678'
}

# Mapeamento dos padrões de RG para cada UF
rg_patterns = {
# Norte
"AC": common_patterns["2_3_groups_dash"],
"AP": common_patterns["2_3_groups_dash"],
"AM": common_patterns["2_3_groups_dash"],
"PA": common_patterns["2_3_groups_dash"],
"RO": common_patterns["2_3_groups_dash"],
"RR": common_patterns["2_3_groups_dash"],
"TO": common_patterns["2_3_groups_dash"],
# Nordeste
"AL": common_patterns["2_3_groups_dash"],
"BA": common_patterns["8_digits_dash"],
"CE": common_patterns["2_3_groups_dash"],
"MA": common_patterns["2_3_groups_dash"],
"PB": common_patterns["9_digits"],
"PE": common_patterns["2_3_groups_dash"],
"PI": common_patterns["2_3_groups_dash"],
"RN": common_patterns["2_3_groups_dash"],
"SE": common_patterns["9_digits"],
# Centro-Oeste
"DF": common_patterns["2_3_groups_dash"],
"GO": common_patterns["2_3_groups_dash"],
"MT": common_patterns["2_3_groups_dash"],
"MS": common_patterns["2_3_groups_dash"],
# Sudeste
"ES": common_patterns["2_3_groups_dash"],
"MG": common_patterns["mg_format"],
# Para RJ, aceita RG composto por 9 dígitos sem formatação
"RJ": common_patterns["9_digits"],
"SP": common_patterns["3_groups_dash"],
# Sul
"PR": common_patterns["3_groups_dash"],
"RS": common_patterns["7_10_digits"],
"SC": common_patterns["3_groups"],
}

# Se a UF não estiver mapeada, retorna False
if uf not in rg_patterns:
return False

# Verifica se o RG corresponde ao padrão esperado para a UF
if not re.match(rg_patterns[uf], rg_cleaned):
return False

# Validação do dígito verificador apenas para São Paulo (SP)
if uf == "SP":
# Formato esperado: "12.345.678-9"
parts = rg_cleaned.split("-")
if len(parts) != 2:
return False
# Remove quaisquer caracteres não numéricos da parte principal
main = re.sub(r"\D", "", parts[0])
check_digit = parts[1]
# O número principal deve ter exatamente 8 dígitos
if len(main) != 8:
return False
# Cálculo: multiplica cada dígito pelos pesos [2, 3, 4, 5, 6, 7, 8, 9]
total = sum(int(d) * w for d, w in zip(main, [2, 3, 4, 5, 6, 7, 8, 9]))
remainder = total % 11
expected = "X" if remainder == 10 else str(remainder)
if check_digit != expected:
return False

return True
42 changes: 42 additions & 0 deletions tests/test_renavam.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from unittest import TestCase

from brutils.renavam import is_valid_renavam


class TestRENAVAM(TestCase):
def test_valid_renavam(self):
# First
self.assertTrue(is_valid_renavam("79831854647"))
self.assertFalse(is_valid_renavam("1234567890")) # Less than 11 digits
self.assertFalse(
is_valid_renavam("123456789012")
) # More than 11 digits
# Second
self.assertFalse(is_valid_renavam("1234567890a")) # With letter
self.assertFalse(is_valid_renavam("12345678 901")) # With space
self.assertFalse(
is_valid_renavam("12345678901!")
) # With special character
self.assertFalse(is_valid_renavam("")) # Empty string

# Third
def test_renavam_check_digit(self):
self.assertTrue(is_valid_renavam("01044683357"))
self.assertFalse(is_valid_renavam("12345678901")) # Invalid check digit

# Fourth - Issue
def test_is_valid_renavam(self):
# Tests for valid RENAVAM
self.assertTrue(
is_valid_renavam("35298206229")
) # Change this one because in the issue example it is invalid
self.assertFalse(is_valid_renavam("12345678900"))

# Tests for invalid RENAVAM
self.assertFalse(is_valid_renavam("1234567890a"))
self.assertFalse(is_valid_renavam("12345678 901"))
self.assertFalse(is_valid_renavam("12345678"))
self.assertFalse(is_valid_renavam(""))
self.assertFalse(is_valid_renavam("123456789012"))
self.assertFalse(is_valid_renavam("abcdefghijk"))
self.assertFalse(is_valid_renavam("12345678901!"))
Loading