Skip to content

Commit fdcec2c

Browse files
feat: CNH add is_valid_cnh function with validation, tests, and docs (#651)
* feat(cnh): add CNH validation function and corresponding tests * fixed formatting * refactor cnh.py * improve naming * fi format * Add documentation * Added changelog entries --------- Co-authored-by: Nilton Pimentel <63605485+niltonpimentel02@users.noreply.github.com>
1 parent 4ac8b60 commit fdcec2c

6 files changed

Lines changed: 170 additions & 0 deletions

File tree

CHANGELOG.md

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

1212
- Utilitário `convert_name_to_uf`
13+
- Utilitário `is_valid_cnh` [#651](https://github.com/brazilian-utils/brutils-python/pull/651)
1314

1415
### Fixed
1516

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ False
6969
- [is\_valid\_email](#is_valid_email)
7070
- [Data](#date)
7171
- [convert\_date\_to_text](#convert_date_to_text)
72+
- [CNH](#cnh)
73+
- [is\_valid\_cnh](#is_valid_cnh)
7274
- [Placa de Carro](#placa-de-carro)
7375
- [is\_valid\_license\_plate](#is_valid_license_plate)
7476
- [format\_license\_plate](#format_license_plate)
@@ -666,6 +668,37 @@ None
666668
"Primeiro de agosto de dois mil e vinte e quatro"
667669
````
668670

671+
## CNH
672+
673+
### is_valid_cnh
674+
675+
Verifica se o número de registro de CNH (Carteira de Habilitação Nacional) brasileiro é válido.
676+
Para que um número de CNH seja considerado válido, a entrada deve ser uma string contendo
677+
exatamente 11 dígitos numéricos. Esta função não verifica se o número da CNH é real, apenas
678+
valida os dígitos verificadores.
679+
680+
Argumentos:
681+
682+
- cnh (str): A string contendo o número de registro de CNH a ser verificado.
683+
684+
Retorno:
685+
686+
- bool: True se o número de registro da CNHN for válido (11 dígitos), False caso contrário.
687+
688+
Exemplo:
689+
690+
```python
691+
>>> from brutils import is_valid_cnh
692+
>>> is_valid_cnh("12345678901")
693+
False
694+
>>> is_valid_cnh("A2C45678901")
695+
False
696+
>>> is_valid_cnh("98765432100")
697+
True
698+
>>> is_valid_cnh("987654321-00")
699+
True
700+
```
701+
669702

670703
## Placa de Carro
671704

README_EN.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ False
6868
- [generate\_phone](#generate_phone)
6969
- [Email](#email)
7070
- [is\_valid\_email](#is_valid_email)
71+
- [CNH](#cnh)
72+
- [is\_valid\_cnh](#is_valid_cnh)
7173
- [License Plate](#license-plate)
7274
- [is\_valid\_license\_plate](#is_valid_license_plate)
7375
- [format\_license\_plate](#format_license_plate)
@@ -659,6 +661,37 @@ False
659661
False
660662
```
661663

664+
665+
## CNH
666+
667+
### is_valid_cnh
668+
669+
Checks if the registration number of a brazilian CNH (Carteira de Habilitação Nacional or Driver's License in En.) is valid.
670+
To be considered valid, the input must be a string containing exactly 11 digits. This function does not verify if the registration number of the CNH is a real one, it only validates it's verification numbers.
671+
672+
Argumentos:
673+
674+
- cnh (str): A string containing the registration nunber of the CNH to be checked.
675+
676+
Retorno:
677+
678+
- bool: True if the CNH number is valid (11 digits), False otherwise.
679+
680+
Exemplo:
681+
682+
```python
683+
>>> from brutils import is_valid_cnh
684+
>>> is_valid_cnh("123456789")
685+
False
686+
>>> is_valid_cnh("A2C45678901")
687+
False
688+
>>> is_valid_cnh("98765432100")
689+
True
690+
>>> is_valid_cnh("987654321-00")
691+
True
692+
```
693+
694+
662695
## License Plate
663696

664697
### is_valid_license_plate

brutils/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
from brutils.cep import is_valid as is_valid_cep
99
from brutils.cep import remove_symbols as remove_symbols_cep
1010

11+
# CNH Imports
12+
from brutils.cnh import is_valid_cnh as is_valid_cnh
13+
1114
# CNPJ Imports
1215
from brutils.cnpj import format_cnpj
1316
from brutils.cnpj import generate as generate_cnpj
@@ -95,6 +98,8 @@
9598
"generate_cpf",
9699
"is_valid_cpf",
97100
"remove_symbols_cpf",
101+
# CNH
102+
"is_valid_cnh",
98103
# Email
99104
"is_valid_email",
100105
# Legal Process

brutils/cnh.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
def is_valid_cnh(cnh: str) -> bool:
2+
"""
3+
Validates the registration number for the Brazilian CNH (Carteira Nacional de Habilitação) that was created in 2022.
4+
Previous versions of the CNH are not supported in this version.
5+
This function checks if the given CNH is valid based on the format and allowed characters,
6+
verifying the verification digits.
7+
8+
Args:
9+
cnh (str): CNH string (symbols will be ignored).
10+
11+
Returns:
12+
bool: True if CNH has a valid format.
13+
14+
Examples:
15+
>>> is_valid_cnh("12345678901")
16+
False
17+
>>> is_valid_cnh("A2C45678901")
18+
False
19+
>>> is_valid_cnh("98765432100")
20+
True
21+
>>> is_valid_cnh("987654321-00")
22+
True
23+
"""
24+
cnh = "".join(
25+
filter(str.isdigit, cnh)
26+
) # clean the input and check for numbers only
27+
28+
if not cnh:
29+
return False
30+
31+
if len(cnh) != 11:
32+
return False
33+
34+
# Reject sequences as "00000000000", "11111111111", etc.
35+
if cnh == cnh[0] * 11:
36+
return False
37+
38+
# cast digits to list of integers
39+
digits: list[int] = [int(ch) for ch in cnh]
40+
first_verificator = digits[9]
41+
second_verificator = digits[10]
42+
43+
if not _check_first_verificator(
44+
digits, first_verificator
45+
): # checking the 10th digit
46+
return False
47+
48+
return _check_second_verificator(
49+
digits, second_verificator, first_verificator
50+
) # checking the 11th digit
51+
52+
53+
def _check_first_verificator(digits: list[int], first_verificator: int) -> bool:
54+
"""
55+
Generates the first verification digit and uses it to verify the 10th digit of the CNH
56+
"""
57+
58+
sum = 0
59+
for i in range(9):
60+
sum += digits[i] * (9 - i)
61+
62+
sum = sum % 11
63+
result = 0 if sum > 9 else sum
64+
65+
return result == first_verificator
66+
67+
68+
def _check_second_verificator(
69+
digits: list[int], second_verificator: int, first_verificator: int
70+
) -> bool:
71+
"""
72+
Generates the second verification and uses it to verify the 11th digit of the CNH
73+
"""
74+
sum = 0
75+
for i in range(9):
76+
sum += digits[i] * (i + 1)
77+
78+
result = sum % 11
79+
80+
if first_verificator > 9:
81+
result = result + 9 if (result - 2) < 0 else result - 2
82+
83+
if result > 9:
84+
result = 0
85+
86+
return result == second_verificator

tests/test_cnh.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from unittest import TestCase
2+
3+
from brutils.cnh import is_valid_cnh
4+
5+
6+
class TestCNH(TestCase):
7+
def test_is_valid_cnh(self):
8+
self.assertFalse(is_valid_cnh("22222222222"))
9+
self.assertFalse(is_valid_cnh("ABC70304734"))
10+
self.assertFalse(is_valid_cnh("6619558737912"))
11+
self.assertTrue(is_valid_cnh("097703047-34"))
12+
self.assertTrue(is_valid_cnh("09770304734"))

0 commit comments

Comments
 (0)