From 7021603c3b2052af0add3fea5d329fb25f4c20d0 Mon Sep 17 00:00:00 2001 From: Joao Fontebasso Date: Tue, 28 Oct 2025 00:07:26 -0300 Subject: [PATCH 1/5] feat(renavam): add is_valid_renavam function with validation, tests, and docs --- README.md | 33 ++++++++++++++++++++++++++++++++ brutils/__init__.py | 5 +++++ brutils/renavam.py | 44 +++++++++++++++++++++++++++++++++++++++++++ tests/test_renavam.py | 17 +++++++++++++++++ 4 files changed, 99 insertions(+) create mode 100644 brutils/renavam.py create mode 100644 tests/test_renavam.py diff --git a/README.md b/README.md index 0b4defb..b621186 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,8 @@ False - [format\_legal\_process](#format_legal_process) - [remove\_symbols\_legal\_process](#remove_symbols_legal_process) - [generate\_legal\_process](#generate_legal_process) +- [RENAVAM](#renavam) + - [is_valid_renavam](#is_valid_renavam) - [Titulo Eleitoral](#titulo-eleitoral) - [is\_valid\_voter\_id](#is_valid_voter_id) - [format\_voter\_id](#format_voter_id) @@ -1342,6 +1344,37 @@ Exemplo: None ``` +## RENAVAM + +### is_valid_renavam + +Valida se os dígitos de verificação do RENAVAM fornecido +correspondem aos seus 10 dígitos iniciais. Esta função não verifica a existência do veículo; +ela apenas valida o formato da string e o dígito verificador. + +Argumentos: + +- renavam (str): O RENAVAM a ser validado, uma string de 11 dígitos. + +Retorna: + +- bool: Verdadeiro se o dígito de verificação corresponder aos 10 dígitos iniciais, + Falso caso contrário. + +Levanta: + +- ValueError: Se a entrada não for uma string, não for numérica ou não tiver exatamente 11 dígitos. + +Exemplo: + +```python +>>> from brutils import is_valid_renavam +>>> is_valid_renavam("86769597308") +True +>>> is_valid_renavam("12345678901") +False +``` + # Novos Utilitários e Reportar Bugs Caso queira sugerir novas funcionalidades ou reportar bugs, basta criar diff --git a/brutils/__init__.py b/brutils/__init__.py index 1bdcfd6..8e12fbb 100644 --- a/brutils/__init__.py +++ b/brutils/__init__.py @@ -71,6 +71,9 @@ 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 + # Voter ID Imports from brutils.voter_id import format_voter_id from brutils.voter_id import generate as generate_voter_id @@ -120,6 +123,8 @@ "generate_pis", "is_valid_pis", "remove_symbols_pis", + #RENAVAM + "is_valid_renavam", # Voter ID "format_voter_id", "generate_voter_id", diff --git a/brutils/renavam.py b/brutils/renavam.py new file mode 100644 index 0000000..c9e445b --- /dev/null +++ b/brutils/renavam.py @@ -0,0 +1,44 @@ +RENAVAM_DV_WEIGHTS = [2, 3, 4, 5, 6, 7, 8, 9, 2, 3] + + +def _validate_renavam_format(renavam: str): + if not isinstance(renavam, str): + raise ValueError("Invalid RENAVAM: must be a string.") + if len(renavam) != 11 or not renavam.isdigit(): + raise ValueError( + "Invalid RENAVAM: must contain exactly 11 numeric digits." + ) + + +def _sum_weighted_digits(renavam: str) -> int: + base_digits = [int(d) for d in renavam[:-1][::-1]] + return sum(x * y for x, y in zip(base_digits, RENAVAM_DV_WEIGHTS)) + + +def _calculate_renavam_dv(renavam: str): + weighted_sum = _sum_weighted_digits(renavam) + dv = 11 - (weighted_sum % 11) + return 0 if dv >= 10 else dv + + +def is_valid_renavam(renavam: 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, with the last digit as + a verification digit calculated from the previous 10 digits. + + Args: + renavam (str): The RENAVAM string to be validated. + + Returns: + bool: True if the RENAVAM is valid, False otherwise. + + Raises: + ValueError: If the input is not a string, contains non-numeric + characters, or does not have exactly 11 digits. + """ + _validate_renavam_format(renavam) + + return _calculate_renavam_dv(renavam) == int(renavam[-1]) diff --git a/tests/test_renavam.py b/tests/test_renavam.py new file mode 100644 index 0000000..a5a3e55 --- /dev/null +++ b/tests/test_renavam.py @@ -0,0 +1,17 @@ +from unittest import TestCase + +from brutils.renavam import is_valid_renavam + + +class TestRENAVAM(TestCase): + def test_is_valid_renavam(self): + self.assertTrue(is_valid_renavam("86769597308")) + self.assertFalse(is_valid_renavam("12345678901")) + + self.assertRaises(ValueError,is_valid_renavam,"1234567890a") + self.assertRaises(ValueError,is_valid_renavam,"12345678 901") + self.assertRaises(ValueError,is_valid_renavam,"12345678") + self.assertRaises(ValueError,is_valid_renavam,"") + self.assertRaises(ValueError,is_valid_renavam,"123456789012") + self.assertRaises(ValueError,is_valid_renavam,"abcdefghijk") + self.assertRaises(ValueError,is_valid_renavam,"12345678901!") From dc3c927a54bbdbd6f5e3dd9119428d18077dc61f Mon Sep 17 00:00:00 2001 From: Joao Fontebasso Date: Tue, 28 Oct 2025 00:15:55 -0300 Subject: [PATCH 2/5] docs: include renavam in readme_en --- README_EN.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README_EN.md b/README_EN.md index 3bdc364..abfdf20 100644 --- a/README_EN.md +++ b/README_EN.md @@ -80,6 +80,8 @@ False - [format\_pis](#format_pis) - [remove\_symbols\_pis](#remove_symbols_pis) - [generate\_pis](#generate_pis) +- [RENAVAM](#renavam) + - [is_valid_renavam](#is_valid_renavam) - [Legal Process](#legal-process) - [is\_valid\_legal\_process](#is_valid_legal_process) - [format\_legal\_process](#format_legal_process) @@ -1346,6 +1348,36 @@ Example: None ``` +## RENAVAM + +### is_valid_renavam + +Validates whether the verification digit of the given RENAVAM +matches its first 10 digits. This function does not check if the vehicle exists; +it only validates the string format and the verification digit. + +Arguments: + +- renavam (str): The RENAVAM to be validated, a string of 11 digits. + +Returns: + +- bool: True if the verification digit matches the first 10 digits, + False otherwise. + +Raises: + +- ValueError: If the input is not a string, is not numeric, or does not have exactly 11 digits. + +Example: + +```python +>>> from brutils import is_valid_renavam +>>> is_valid_renavam("86769597308") +True +>>> is_valid_renavam("12345678901") +False + # Feature Request and Bug Report If you want to suggest new features or report bugs, simply create From 8d73f17f532699d496c8032112ce4cace63e748a Mon Sep 17 00:00:00 2001 From: Joao Fontebasso Date: Tue, 28 Oct 2025 00:20:23 -0300 Subject: [PATCH 3/5] style: apply formatting --- brutils/__init__.py | 2 +- tests/test_renavam.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/brutils/__init__.py b/brutils/__init__.py index 8e12fbb..85de97b 100644 --- a/brutils/__init__.py +++ b/brutils/__init__.py @@ -123,7 +123,7 @@ "generate_pis", "is_valid_pis", "remove_symbols_pis", - #RENAVAM + # RENAVAM "is_valid_renavam", # Voter ID "format_voter_id", diff --git a/tests/test_renavam.py b/tests/test_renavam.py index a5a3e55..455da70 100644 --- a/tests/test_renavam.py +++ b/tests/test_renavam.py @@ -8,10 +8,10 @@ def test_is_valid_renavam(self): self.assertTrue(is_valid_renavam("86769597308")) self.assertFalse(is_valid_renavam("12345678901")) - self.assertRaises(ValueError,is_valid_renavam,"1234567890a") - self.assertRaises(ValueError,is_valid_renavam,"12345678 901") - self.assertRaises(ValueError,is_valid_renavam,"12345678") - self.assertRaises(ValueError,is_valid_renavam,"") - self.assertRaises(ValueError,is_valid_renavam,"123456789012") - self.assertRaises(ValueError,is_valid_renavam,"abcdefghijk") - self.assertRaises(ValueError,is_valid_renavam,"12345678901!") + self.assertRaises(ValueError, is_valid_renavam, "1234567890a") + self.assertRaises(ValueError, is_valid_renavam, "12345678 901") + self.assertRaises(ValueError, is_valid_renavam, "12345678") + self.assertRaises(ValueError, is_valid_renavam, "") + self.assertRaises(ValueError, is_valid_renavam, "123456789012") + self.assertRaises(ValueError, is_valid_renavam, "abcdefghijk") + self.assertRaises(ValueError, is_valid_renavam, "12345678901!") From 0deb6b76769d2a77b3e46bca48b47b62bf33f169 Mon Sep 17 00:00:00 2001 From: Joao Fontebasso Date: Tue, 28 Oct 2025 21:22:32 -0300 Subject: [PATCH 4/5] fix: Removed Raise ValueError() to return False. Updated unit tests and documentation --- README.md | 6 +----- README_EN.md | 6 +----- brutils/renavam.py | 28 ++++++++++++++++++++-------- tests/test_renavam.py | 16 +++++++++------- 4 files changed, 31 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index b621186..d8dbdef 100644 --- a/README.md +++ b/README.md @@ -1358,13 +1358,9 @@ Argumentos: Retorna: -- bool: Verdadeiro se o dígito de verificação corresponder aos 10 dígitos iniciais, +- bool: Verdadeiro se o RENAVAM for válido Falso caso contrário. -Levanta: - -- ValueError: Se a entrada não for uma string, não for numérica ou não tiver exatamente 11 dígitos. - Exemplo: ```python diff --git a/README_EN.md b/README_EN.md index abfdf20..5ecbb0c 100644 --- a/README_EN.md +++ b/README_EN.md @@ -1362,13 +1362,9 @@ Arguments: Returns: -- bool: True if the verification digit matches the first 10 digits, +- bool: True if RENAVAM is valid, False otherwise. -Raises: - -- ValueError: If the input is not a string, is not numeric, or does not have exactly 11 digits. - Example: ```python diff --git a/brutils/renavam.py b/brutils/renavam.py index c9e445b..a536245 100644 --- a/brutils/renavam.py +++ b/brutils/renavam.py @@ -3,11 +3,12 @@ def _validate_renavam_format(renavam: str): if not isinstance(renavam, str): - raise ValueError("Invalid RENAVAM: must be a string.") + return False if len(renavam) != 11 or not renavam.isdigit(): - raise ValueError( - "Invalid RENAVAM: must contain exactly 11 numeric digits." - ) + return False + if len(set(renavam)) == 1: + return False + return True def _sum_weighted_digits(renavam: str) -> int: @@ -35,10 +36,21 @@ def is_valid_renavam(renavam: str) -> bool: Returns: bool: True if the RENAVAM is valid, False otherwise. - Raises: - ValueError: If the input is not a string, contains non-numeric - characters, or does not have exactly 11 digits. + Example: + >>> is_valid_renavam('86769597308') + True + >>> is_valid_renavam('12345678901') + False + >>> is_valid_renavam('1234567890a') + False + >>> is_valid_renavam('12345678 901') + False + >>> is_valid_renavam('12345678') + False + >>> is_valid_renavam('') + False """ - _validate_renavam_format(renavam) + if not _validate_renavam_format(renavam): + return False return _calculate_renavam_dv(renavam) == int(renavam[-1]) diff --git a/tests/test_renavam.py b/tests/test_renavam.py index 455da70..5c2850a 100644 --- a/tests/test_renavam.py +++ b/tests/test_renavam.py @@ -8,10 +8,12 @@ def test_is_valid_renavam(self): self.assertTrue(is_valid_renavam("86769597308")) self.assertFalse(is_valid_renavam("12345678901")) - self.assertRaises(ValueError, is_valid_renavam, "1234567890a") - self.assertRaises(ValueError, is_valid_renavam, "12345678 901") - self.assertRaises(ValueError, is_valid_renavam, "12345678") - self.assertRaises(ValueError, is_valid_renavam, "") - self.assertRaises(ValueError, is_valid_renavam, "123456789012") - self.assertRaises(ValueError, is_valid_renavam, "abcdefghijk") - self.assertRaises(ValueError, is_valid_renavam, "12345678901!") + 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!")) + self.assertFalse(is_valid_renavam("00000000000")) + self.assertFalse(is_valid_renavam("11111111111")) From a7140f0c70edfa3f12de232a7a9167d8d0c674b3 Mon Sep 17 00:00:00 2001 From: Joao Fontebasso Date: Tue, 4 Nov 2025 22:48:36 -0300 Subject: [PATCH 5/5] fix: include function in section unreleased and remove empty line --- CHANGELOG.md | 1 + tests/test_renavam.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c37db5..876fb66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Utilitário `convert_name_to_uf` - Utilitário `is_valid_cnh` [#651](https://github.com/brazilian-utils/brutils-python/pull/651) +- Utilitário `is_valid_renavam` [#652](https://github.com/brazilian-utils/brutils-python/pull/652) ### Fixed diff --git a/tests/test_renavam.py b/tests/test_renavam.py index 5c2850a..8ff900e 100644 --- a/tests/test_renavam.py +++ b/tests/test_renavam.py @@ -7,7 +7,6 @@ class TestRENAVAM(TestCase): def test_is_valid_renavam(self): self.assertTrue(is_valid_renavam("86769597308")) self.assertFalse(is_valid_renavam("12345678901")) - self.assertFalse(is_valid_renavam("1234567890a")) self.assertFalse(is_valid_renavam("12345678 901")) self.assertFalse(is_valid_renavam("12345678"))