diff --git a/CHANGELOG.md b/CHANGELOG.md index a8d204d..b9fef37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Utilitário `list_all_legal_nature` [#653](https://github.com/brazilian-utils/python/pull/653) - 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) +- Utilitário `is_valid_passport` [#579](https://github.com/brazilian-utils/python/issues/579) +- Utilitário `generate_passport` [#579](https://github.com/brazilian-utils/python/issues/579) +- Utilitário `format_passport` [#579](https://github.com/brazilian-utils/python/issues/579) +- Utilitário `remove_symbols_passport` [#579](https://github.com/brazilian-utils/python/issues/579) ### Fixed diff --git a/README.md b/README.md index a24fffa..c9ccefa 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,6 @@ False ``` # Utilitários - - [CPF](#cpf) - [is\_valid\_cpf](#is_valid_cpf) - [format\_cpf](#format_cpf) @@ -109,6 +108,11 @@ False - [is_valid_legal_nature](#is_valid_legal_nature) - [get_legal_nature_description](#get_legal_nature_description) - [list_all_legal_nature](#list_all_legal_nature) +- [Passaporte](#passaporte) + - [is_valid_passport](#is_valid_passport) + - [format_passport](#format_passport) + - [remove_symbols_passport](#remove_symbols_passport) + - [generate_passport](#generate_passport) ## CPF @@ -1446,6 +1450,8 @@ Retorna uma cópia do dicionário completo `{codigo: descricao}`. True >>> data["2062"] 'Sociedade Empresária Limitada' +``` + ## RENAVAM ### is_valid_renavam @@ -1473,6 +1479,94 @@ True False ``` +## Passaporte + +### is_valid_passport + +Verifica se um número de passaporte brasileiro é válido. + +Para ser considerado válido, a entrada deve ser uma string contendo exatamente dois caracteres alfabéticos seguidos de exatamente seis dígitos numéricos. + +Esta função não verifica se a entrada é um número de passaporte real, pois não existem dígitos verificadores para o passaporte brasileiro. + +Argumentos: +- passport (str): A string contendo o número do passaporte a ser verificado. + +Retorna: +- bool: True se o número do passaporte for válido (2 letras seguidas de 6 dígitos). False caso contrário. + +Exemplo: +```python +>>> from brutils import is_valid_passport +>>> is_valid_passport("Ab123456") +True +>>> is_valid_passport("12345678") +False +>>> is_valid_passport("DC-221345") +False +``` +### format_passport + +Formata um número de passaporte brasileiro para exibição. + +Esta função recebe uma string representando um número de passaporte válido e o retorna formatado (maiúsculas, sem símbolos). + +Argumentos: +- passport (str | None): Um número de passaporte brasileiro (minúsculas ou maiúsculas, possivelmente incluindo símbolos) + +Retorna: +- str: O número do passaporte formatado (maiúsculas, sem símbolos) ou None se a entrada for inválida + +Exemplo: +```python +>>> from brutils import format_passport +>>> format_passport("Ab123456") +AB123456 +>>> format_passport("Ab-123456") +AB123456 +>>> format_passport("111111") +None +``` +### remove_symbols_passport + +Remove símbolos ('-', '.' e espaços em branco) de um número de passaporte. + +Esta função recebe uma string com um número de passaporte como entrada e remove todas as ocorrências dos caracteres '.', '-' e espaço em branco. + +Argumentos: +- passport (str): A string contendo um número de passaporte + +Retorna: +- str: O número do passaporte com hífens (-), pontos (.) e espaços em branco ( ) removidos. + +Exemplo: +```python +>>> from brutils import remove_symbols_passport +>>> remove_symbols_passport("Ab123456") +Ab123456 +>>> remove_symbols_passport("Ab-123456") +Ab123456 +>>> remove_symbols_passport("Ab -. 123456") +Ab123456 +``` +### generate_passport + +Gera uma string com um número de passaporte brasileiro válido aleatório. + +Esta função gera uma string com um número de passaporte brasileiro aleatório. + +Retorna: +- str: Uma string com um número de passaporte válido aleatório. + +Exemplo: +```python +>>> from brutils import generate_passport +>>> generate_passport() +"RY393097" +>>> generate_passport() +"ZS840088" +``` + # Novos Utilitários e Reportar Bugs Caso queira sugerir novas funcionalidades ou reportar bugs, basta criar diff --git a/README_EN.md b/README_EN.md index aa784da..559f8f5 100644 --- a/README_EN.md +++ b/README_EN.md @@ -40,7 +40,6 @@ False ``` # Utilities - - [CPF](#cpf) - [is\_valid\_cpf](#is_valid_cpf) - [format\_cpf](#format_cpf) @@ -108,6 +107,11 @@ False - [is_valid_legal_nature](#is_valid_legal_nature) - [get_legal_nature_description](#get_legal_nature_description) - [list_all_legal_nature](#list_all_legal_nature) +- [Passport](#passport) + - [is_valid_passport](#is_valid_passport) + - [format_passport](#format_passport) + - [remove_symbols_passport](#remove_symbols_passport) + - [generate_passport](#generate_passport) ## CPF @@ -1476,6 +1480,95 @@ Example: True >>> is_valid_renavam("12345678901") False +``` + +## Passport + +### is_valid_passport + +Checks if a Brazilian passport number is valid. + +To be considered valid, the input must be a string containing exactly two alphabetical characters followed by exactly six numerical digits. + +This function does not verify is the input is a real passport number, as there are no checksums for the Brazilian passport. + +Args: +- passport (str): The string containing the passport number to be checked. + +Returns: +- bool: True if the passport number is valid (2 letters followed by 6 digits). False otherwise. + +Example: +```python +>>> from brutils import is_valid_passport +>>> is_valid_passport("Ab123456") +True +>>> is_valid_passport("12345678") +False +>>> is_valid_passport("DC-221345") +False +``` +### format_passport + +Formats a Brazilian passport number for display. + +This function takes a string representing a valid passport number and returns it formatted (uppercase, without symbols). + +Args: +- passport (str | None): A Brazilian passport number (lower or uppercase, possibly including symbols) + +Returns: +- str: The formatted passport number (uppercase, without symbols) or None if the input is invalid + +Example: +```python +>>> format_passport("Ab123456") +AB123456 +>>> format_passport("Ab-123456") +AB123456 +>>> format_passport("111111") +None +``` + +### remove_symbols_passport + +Removes symbols ('-', '.', and whitespaces) from a passport number. + +This function takes a passport number string as input and removes all occurrences of +the '.', '-', and whitespace characters from it. + +Args: +- passport (str): The string containing a passport number + +Returns: +- str: The passport numbers with dashes (-), dots (.), and whitespaces ( ) removed. + +Example: +```python +>>> remove_symbols_passport("Ab123456") +Ab123456 +>>> remove_symbols_passport("Ab-123456") +Ab123456 +>>> remove_symbols_passport("Ab -. 123456") +Ab123456 +``` + +### generate_passport + +Generate a random valid Brazilian passport number string. + +This function generates a random Brazilian passport number string. + +Returns: +- str: A random valid passport number string. + +Example: +```python +>>> generate() +"RY393097" +>>> generate() +"ZS840088" +``` # Feature Request and Bug Report diff --git a/brutils/__init__.py b/brutils/__init__.py index 7ba6e25..ddb264e 100644 --- a/brutils/__init__.py +++ b/brutils/__init__.py @@ -64,6 +64,12 @@ from brutils.license_plate import is_valid as is_valid_license_plate from brutils.license_plate import remove_symbols as remove_symbols_license_plate +# Passport Imports +from brutils.passport import format_passport +from brutils.passport import generate as generate_passport +from brutils.passport import is_valid as is_valid_passport +from brutils.passport import remove_symbols as remove_symbols_passport + # Phone Imports from brutils.phone import ( format_phone, @@ -155,4 +161,9 @@ "is_valid_legal_nature", "get_natureza_legal_nature", "list_all_legal_nature", + # Passport + "is_valid_passport", + "remove_symbols_passport", + "format_passport", + "generate_passport", ] diff --git a/brutils/passport.py b/brutils/passport.py new file mode 100644 index 0000000..4c07781 --- /dev/null +++ b/brutils/passport.py @@ -0,0 +1,107 @@ +import random +import re +import string + + +def is_valid(passport: str) -> bool: + """ + Checks if a Brazilian passport number is valid. + + To be considered valid, the input must be a string containing exactly two alphabetical characters followed by exactly six numerical digits. + + This function does not verify is the input is a real passport number, as there are no checksums for the Brazilian passport. + + Args: + passport (str): The string containing the passport number to be checked. + + Returns: + bool: True if the passport number is valid (2 letters followed by 6 digits). False otherwise. + + Example: + >>> is_valid("Ab123456") + True + >>> is_valid("12345678") + False + >>> is_valid("DC-221345") + False + """ + + if not isinstance(passport, str): + return False + + pattern = re.compile("^[A-Z]{2}[0-9]{6}$") + match = re.match(pattern, passport) + return match is not None + + +def remove_symbols(passport: str) -> str: + """ + Removes symbols ('-', '.', and whitespaces) from a passport number. + + This function takes a passport number string as input and removes all occurrences of + the '.', '-', and whitespace characters from it. + + Args: + passport (str): The string containing a passport number + + Returns: + str: The passport numbers with dashes (-), dots (.), and whitespaces ( ) removed. + + Example: + >>> remove_symbols("Ab123456") + Ab123456 + >>> remove_symbols("Ab-123456") + Ab123456 + >>> remove_symbols("Ab -. 123456") + Ab123456 + """ + + return "".join(filter(lambda c: c not in ".- ", passport)) + + +def format_passport(passport: str) -> str | None: + """ + Formats a Brazilian passport number for display. + + This function takes a string representing a valid passport number and returns it formatted (uppercase, without symbols). + + Args: + passport (str | None): A Brazilian passport number (lower or uppercase, possibly including symbols) + + Returns: + str: The formatted passport number (uppercase, without symbols) or None if the input is invalid + + Example: + >>> format_passport("Ab123456") + AB123456 + >>> format_passport("Ab-123456") + AB123456 + >>> format_passport("111111") + None + """ + + passport = remove_symbols(passport.upper()) + + return passport if is_valid(passport) else None + + +def generate() -> str: + """ + Generate a random valid Brazilian passport number string. + + This function generates a random Brazilian passport number string. + + Returns: + str: A random valid passport number string. + + Example: + >>> generate() + "RY393097" + >>> generate() + "ZS840088" + """ + + letters = "".join(random.choices(string.ascii_uppercase, k=2)) + digits = "".join(random.choices(string.digits, k=6)) + + return f"{letters}{digits}" diff --git a/tests/test_passport.py b/tests/test_passport.py new file mode 100644 index 0000000..e75621e --- /dev/null +++ b/tests/test_passport.py @@ -0,0 +1,62 @@ +from unittest import TestCase, main +from unittest.mock import patch + +from brutils.passport import format_passport, generate, is_valid, remove_symbols + + +class TestCPF(TestCase): + def test_is_valid(self): + # When passport is not string, returns False + self.assertIs(is_valid(1), False) # type: ignore + + # When passport's len is different of 8, returns False + self.assertIs(is_valid("1"), False) + + # When passport does not contain only digits, returns False + self.assertIs(is_valid("1112223334-"), False) + + # When cpf is valid + self.assertIs(is_valid("AA111111"), True) + self.assertIs(is_valid("CL125167"), True) + + def test_generate(self): + for _ in range(10_000): + self.assertIs(is_valid(generate()), True) + + def test_remove_symbols(self): + # When there are no symbols, returns the same string + self.assertEqual(remove_symbols("Ab123456"), "Ab123456") + + # When there are spaces, returns the string without them + self.assertEqual(remove_symbols(" AB 123 456 "), "AB123456") + + # When there are dashes, returns the string without them + self.assertEqual(remove_symbols("-AB1-23-4-56-"), "AB123456") + + # When there are dots, returns the string without them + self.assertEqual(remove_symbols(".AB.1.23.456."), "AB123456") + + # When there are multiple symbols, returns the string without any of them + self.assertEqual(remove_symbols(".A B.1.2-3.45 -. 6."), "AB123456") + + +@patch("brutils.passport.is_valid") +class TestIsValidToFormat(TestCase): + def test_when_passport_is_valid_returns_true_to_format(self, mock_is_valid): + mock_is_valid.return_value = True + + # When passport is_valid, returns formatted passport + self.assertEqual(format_passport("yz 987654"), "YZ987654") + + # Checks if function is_valid_passport is called + mock_is_valid.assert_called_once_with("YZ987654") + + def test_when_cpf_is_not_valid_returns_none(self, mock_is_valid): + mock_is_valid.return_value = False + + # When passport isn't valid, returns None + self.assertIsNone(format_passport("acd12736")) + + +if __name__ == "__main__": + main()