From f47fa7df286ac0d7331e6994e2807d0bb8d5bd04 Mon Sep 17 00:00:00 2001 From: kaioduarte Date: Wed, 4 Oct 2023 19:01:50 +0100 Subject: [PATCH 1/2] feat: add is_valid_pis --- CHANGELOG.md | 1 + brutils/__init__.py | 5 ++++ brutils/pis.py | 57 ++++++++++++++++++++++++++++++++++++++++++++ tests/test_pis.py | 58 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+) create mode 100644 brutils/pis.py create mode 100644 tests/test_pis.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fdee781..ca575311 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Utilitário `is_valid_pis` [#216](https://github.com/brazilian-utils/brutils-python/pull/216) - Utilitário `is_valid_email` [#213](https://github.com/brazilian-utils/brutils-python/pull/213) - Utilitário `iis_valid_license_plate_old_format` [#174](https://github.com/brazilian-utils/brutils-python/pull/174) - Utilitário `is_valid_license_plate_old_format` [#174](https://github.com/brazilian-utils/brutils-python/pull/174) diff --git a/brutils/__init__.py b/brutils/__init__.py index a50b1a2a..8bc0b0dd 100644 --- a/brutils/__init__.py +++ b/brutils/__init__.py @@ -27,4 +27,9 @@ ) from brutils.email import is_valid as is_valid_email + from brutils.license_plate import is_valid_license_plate_old_format + +from brutils.pis import ( + is_valid as is_valid_pis, +) diff --git a/brutils/pis.py b/brutils/pis.py new file mode 100644 index 00000000..ddb213ba --- /dev/null +++ b/brutils/pis.py @@ -0,0 +1,57 @@ +WEIGHTS = [3, 2, 9, 8, 7, 6, 5, 4, 3, 2] + + +# OPERATIONS +############ + + +def validate(pis: str) -> bool: + """ + Validate a Brazilian PIS number. + + The PIS is valid if: + - It has 11 digits + - All characters are digits + - It passes the weight calculation check + + Args: + pis[str]: PIS number as a string. + + Returns: + value[bool]: True if PIS is valid, False otherwise. + """ + if len(pis) != 11 or not pis.isdigit(): + return False + + return pis[-1] == str(_checksum(pis[:-1])) + + +def is_valid(pis: str) -> bool: + """ + Returns whether or not the verifying checksum digit of the + given `pis` match its base number. + + Args: + pis[str]: PIS number as a string of proper length. + + Returns: + value[bool]: True if PIS is valid, False otherwise. + """ + return isinstance(pis, str) and validate(pis) + + +def _checksum(base_pis: str) -> int: + """ + Calculates the checksum digit of the given `base_pis` string. + + Args: + base_pis [str]: 10 first digits of PIS number as a string. + + Returns: + value [int]: Checksum digit. + """ + pis_digits = list(map(int, base_pis)) + pis_sum = sum(digit * weight for digit, weight in zip(pis_digits, WEIGHTS)) + check_digit = 11 - (pis_sum % 11) + + return 0 if check_digit in [10, 11] else check_digit diff --git a/tests/test_pis.py b/tests/test_pis.py new file mode 100644 index 00000000..bd81287f --- /dev/null +++ b/tests/test_pis.py @@ -0,0 +1,58 @@ +from os import pardir +from os.path import abspath, join, dirname +from sys import path, version_info, dont_write_bytecode +from inspect import getsourcefile +from unittest.mock import patch + +dont_write_bytecode = True +range = range if version_info.major >= 3 else xrange +path.insert( + 1, abspath(join(dirname(abspath(getsourcefile(lambda: 0))), pardir)) +) +from brutils.pis import validate, is_valid, _checksum +from unittest import TestCase, main + + +class TestPIS(TestCase): + def test_validate(self): + self.assertIs(validate("123456789ab"), False) + self.assertIs(validate("901.85930.32-3"), False) + self.assertIs(validate("90185930323"), True) + self.assertIs(validate("93616217820"), True) + + def test_is_valid(self): + # When PIS is not string, returns False + self.assertIs(is_valid(1), False) + self.assertIs(is_valid([]), False) + self.assertIs(is_valid({}), False) + self.assertIs(is_valid(None), False) + + # When PIS's len is different of 11, returns False + self.assertIs(is_valid("123456789"), False) + + # When PIS does not contain only digits, returns False + self.assertIs(is_valid("123pis"), False) + + # When checksum digit doesn't match last digit, returns False + self.assertIs(is_valid("11111111111"), False) + self.assertIs(is_valid("11111111215"), False) + self.assertIs(is_valid("12038619493"), False) + + # When PIS is valid + self.assertIs(is_valid("12038619494"), True) + self.assertIs(is_valid("12016784018"), True) + self.assertIs(is_valid("12083210826"), True) + + def test_checksum(self): + # Checksum digit is 0 when the subtracted number is 10 or 11 + self.assertEqual(_checksum("1204152015"), 0) + self.assertEqual(_checksum("1204433157"), 0) + + # Checksum digit is equal the subtracted number + self.assertEqual(_checksum("1204917738"), 2) + self.assertEqual(_checksum("1203861949"), 4) + self.assertEqual(_checksum("1208321082"), 6) + + +if __name__ == "__main__": + main() From b76845e2885620f72fca7db353dd19ae14493bbd Mon Sep 17 00:00:00 2001 From: kaioduarte Date: Wed, 4 Oct 2023 19:42:53 +0100 Subject: [PATCH 2/2] chore: update README --- README.md | 20 ++++++++++++++++++++ README_EN.md | 20 ++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/README.md b/README.md index 89993841..c84c2ac0 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,8 @@ False - [is_valid_license_plate_old_format](#is_valid_license_plate_old_format) - [License Plate](#license_plate) - [is_valid_license_plate_mercosul](#is_valid_license_plate_mercosul) +- [PIS](#pis) + - [is_valid_pis](#is_valid_pis) ## CPF @@ -316,6 +318,24 @@ normas do Mercosul, isto é, seguindo o padrão LLLNLNN. True ``` +## PIS + +### is_valid_pis + +Verifica se o número PIS/PASEP é valido. Apenas números, formatados como string. Não verifica se o PIS/PASEP existe. +Mais detalhes sobre a validação estão disponíveis em https://www.macoratti.net/alg_pis.htm. + +```python +from brutils import is_valid_pis + +>>> is_valid_pis("12038619494") +True +>>> is_valid_pis("11111111111") +False +>>> is_valid_pis("123456") +False +``` + # 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 d0d69fb0..4adf5b02 100644 --- a/README_EN.md +++ b/README_EN.md @@ -68,6 +68,8 @@ False - [is_valid_license_plate_old_format](#is_valid_license_plate_old_format) - [License Plate](#license_plate) - [is_valid_license_plate_mercosul](#is_valid_license_plate_mercosul) +- [PIS](#pis) + - [is_valid_pis](#is_valid_pis) ## CPF @@ -291,6 +293,24 @@ Mercosul standards, in other words, if it follows the pattern LLLNLNN. True ``` +## PIS + +### is_valid_pis + +Check if PIS/PASEP number is valid. Numbers only, formatted as strings. Does not check if PIS/PASEP exists. +More details about the validation can be found here: https://www.macoratti.net/alg_pis.htm. + +```python +from brutils import is_valid_pis + +>>> is_valid_pis("12038619494") +True +>>> is_valid_pis("11111111111") +False +>>> is_valid_pis("123456") +False +``` + # Feature Request and Bug Report If you want to suggest new features or report bugs, simply create