Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refatoracao do codigo dos utilitarios de cpf #105

Merged
merged 1 commit into from
Jun 24, 2023
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
115 changes: 44 additions & 71 deletions brutils/cpf.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,31 @@

def sieve(dirty): # type: (str) -> str
"""
Filters out CPF formatting symbols. Symbols that are not used
in the CPF formatting are left unfiltered on purpose so that
if fails other tests, because their presence indicate that the
input was somehow corrupted.
Filters out CPF formatting symbols.

Symbols that are not used in the CPF formatting are left
unfiltered on purpose so that if fails other tests,
because their presence indicate that the input was somehow corrupted.
"""

return "".join(filter(lambda char: char not in ".-", dirty))


def parse(dirty): # type: (str) -> str
"""
Filters out CPF formatting symbols. Symbols that are not used
in the CPF formatting are left unfiltered on purpose so that
if fails other tests, because their presence indicate that the
input was somehow corrupted.
"""

"""Alias to the function `sieve`. Better naming."""
return sieve(dirty)


def display(cpf): # type: (str) -> str
"""
Will format an adequately formatted numbers-only CPF string,
Format an adequately formatted numbers-only CPF string,
adding in standard formatting visual aid symbols for display.
Backcompatibility for Version 1.0.1.
"""

if not cpf.isdigit() or len(cpf) != 11 or len(set(cpf)) == 1:
return None

return "{}.{}.{}-{}".format(cpf[:3], cpf[3:6], cpf[6:9], cpf[9:])


Expand All @@ -46,37 +44,8 @@ def format_cpf(cpf): # type: (str) -> str

if not is_valid(cpf):
return None
return "{}.{}.{}-{}".format(cpf[:3], cpf[3:6], cpf[6:9], cpf[9:])


# CALCULATORS
#############


def hashdigit(cpf, position): # type: (str, int) -> int
"""
Will compute the given `position` checksum digit for the `cpf`
input. The input needs to contain all elements previous to
`position` else computation will yield the wrong result.
"""
val = (
sum(
int(digit) * weight
for digit, weight in zip(cpf, range(position, 1, -1))
)
% 11
)
return 0 if val < 2 else 11 - val


def checksum(basenum): # type: (str) -> str
"""
Will compute the checksum digits for a given CPF base number.
`basenum` needs to be a digit-string of adequate length.
"""
verifying_digits = str(hashdigit(basenum, 10))
verifying_digits += str(hashdigit(basenum + verifying_digits, 11))
return verifying_digits
return "{}.{}.{}-{}".format(cpf[:3], cpf[3:6], cpf[6:9], cpf[9:])


# OPERATIONS
Expand All @@ -88,54 +57,58 @@ def validate(cpf): # type: (str) -> bool
Returns whether or not the verifying checksum digits of the
given `cpf` match it's base number. Input should be a digit
string of proper length.

Source: https://www.geradorcpf.com/algoritmo_do_cpf.htm
Backcompatibility for Version 1.0.1.
"""

if not cpf.isdigit() or len(cpf) != 11 or len(set(cpf)) == 1:
return False
return all(hashdigit(cpf, i + 10) == int(v) for i, v in enumerate(cpf[9:]))


def generate(): # type: () -> str
"""Generates a random valid CPF digit string."""
base = str(randint(1, 999999998)).zfill(9)
while len(set(base)) == 1:
base = str(randint(1, 999999998)).zfill(9)
return base + checksum(base)
return all(_hashdigit(cpf, i + 10) == int(v) for i, v in enumerate(cpf[9:]))


def is_valid(cpf): # type: (str) -> bool
"""
Returns whether or not a cpf is_valid.
Source: https://www.geradorcpf.com/algoritmo_do_cpf.htm
Evaluates that cpf is String and calls validate.
"""
is_syntax_valid = isinstance(cpf, str) and len(cpf) == 11 and cpf.isdigit()

return is_syntax_valid and _is_semantic_valid(cpf)
return isinstance(cpf, str) and validate(cpf)


def _is_semantic_valid(cpf):
cpf = [int(digit) for digit in cpf]
def generate(): # type: () -> str
"""Generates a random valid CPF digit string."""

constants_tenth_digit = [10, 9, 8, 7, 6, 5, 4, 3, 2]
is_tenth_digit_valid = _is_digit_valid(cpf, constants_tenth_digit, 9)
base = str(randint(1, 999999998)).zfill(9)

constants_eleventh_digit = [11, 10, 9, 8, 7, 6, 5, 4, 3, 2]
is_eleventh_digit_valid = _is_digit_valid(cpf, constants_eleventh_digit, 10)
return base + _checksum(base)

return is_tenth_digit_valid and is_eleventh_digit_valid

def _hashdigit(cpf, position): # type: (str, int) -> int
"""
Will compute the given `position` checksum digit for the `cpf`
input. The input needs to contain all elements previous to
`position` else computation will yield the wrong result.
"""

def _is_digit_valid(cpf, constants, digit_index):
sum = _multiply_and_sum_lists(cpf, constants, digit_index)
rest = sum % 11
digit = cpf[digit_index]
val = (
sum(
int(digit) * weight
for digit, weight in zip(cpf, range(position, 1, -1))
)
% 11
)

return (rest <= 2 and digit == 0) or (rest > 2 and digit == (11 - rest))
return 0 if val < 2 else 11 - val


def _multiply_and_sum_lists(list_1, list_2, max_index):
sum = 0
def _checksum(basenum): # type: (str) -> str
"""
Will compute the checksum digits for a given CPF base number.
`basenum` needs to be a digit-string of adequate length.
"""

for index in range(0, max_index):
sum += list_1[index] * list_2[index]
verifying_digits = str(_hashdigit(basenum, 10))
verifying_digits += str(_hashdigit(basenum + verifying_digits, 11))

return sum
return verifying_digits
39 changes: 20 additions & 19 deletions tests/test_cpf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
from brutils.cpf import (
sieve,
display,
hashdigit,
checksum,
validate,
generate,
is_valid,
format_cpf,
parse,
_hashdigit,
_checksum,
)
from unittest import TestCase, main

Expand All @@ -33,12 +33,10 @@ def test_sieve(self):
assert sieve("...---...") == ""

def test_parse(self):
assert parse("00000000000") == "00000000000"
assert parse("123.456.789-10") == "12345678910"
assert parse("134..2435.-1892.-") == "13424351892"
assert parse("abc1230916*!*&#") == "abc1230916*!*&#"
assert parse("ab.c1.--.2-309.-1-.6-.*.-!*&#") == "abc1230916*!*&#"
assert parse("...---...") == ""
with patch("brutils.cpf.sieve") as mock_sieve:
# When call parse, it calls sieve
parse("123.456.789-10")
mock_sieve.assert_called()

def test_display(self):
assert display("00000000011") == "000.000.000-11"
Expand All @@ -58,16 +56,6 @@ def test_format_cpf(self):
# When cpf isn't valid, returns None
assert format_cpf("11144477735") is None

def test_hashdigit(self):
assert hashdigit("000000000", 10) == 0
assert hashdigit("0000000000", 11) == 0
assert hashdigit("52513127765", 10) == 6
assert hashdigit("52513127765", 11) == 5

def test_checksum(self):
assert checksum("000000000") == "00"
assert checksum("525131277") == "65"

def test_validate(self):
assert validate("52513127765")
assert validate("52599927765")
Expand All @@ -83,6 +71,9 @@ def test_is_valid(self):
# When cpf does not contain only digits, returns False
assert not is_valid("1112223334-")

# When CPF has only the same digit, returns false
assert not is_valid("11111111111")

# When rest_1 is lt 2 and the 10th digit is not 0, returns False
assert not is_valid("11111111215")

Expand All @@ -100,10 +91,20 @@ def test_is_valid(self):
assert is_valid("11111111200")

def test_generate(self):
for i in range(1000):
for _ in range(10_000):
assert validate(generate())
assert display(generate()) is not None

def test__hashdigit(self):
assert _hashdigit("000000000", 10) == 0
assert _hashdigit("0000000000", 11) == 0
assert _hashdigit("52513127765", 10) == 6
assert _hashdigit("52513127765", 11) == 5

def test_checksum(self):
assert _checksum("000000000") == "00"
assert _checksum("525131277") == "65"


if __name__ == "__main__":
main()