Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions brutils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
57 changes: 57 additions & 0 deletions brutils/pis.py
Original file line number Diff line number Diff line change
@@ -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
58 changes: 58 additions & 0 deletions tests/test_pis.py
Original file line number Diff line number Diff line change
@@ -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()