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
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 8.14.0
current_version = 8.14.1
files = VERSION faker/__init__.py docs/conf.py
commit = True
tag = True
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## Changelog

### [v8.14.1 - 2021-10-01](https://github.com/joke2k/faker/compare/v8.14.0...v8.14.1)

* Fix `pydecimal` precision. Thanks @joidegn.

### [v8.14.0 - 2021-09-24](https://github.com/joke2k/faker/compare/v8.13.1...v8.14.0)

* Add datetime provider for ``bn_BD``. Thanks @jabertuhin.
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ Examples:
94812 Biedenkopf

$ faker profile ssn,birthdate
{'ssn': u'628-10-1085', 'birthdate': '2008-03-29'}
{'ssn': '628-10-1085', 'birthdate': '2008-03-29'}

$ faker -r=3 -s=";" name
Willam Kertzmann;
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
8.14.0
8.14.1
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@
# built documents.
#
# The short X.Y version.
version = '8.14.0'
version = '8.14.1'
# The full version, including alpha/beta/rc tags.
release = '8.14.0'
release = '8.14.1'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
2 changes: 1 addition & 1 deletion faker/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
from faker.generator import Generator
from faker.proxy import Faker

VERSION = '8.14.0'
VERSION = '8.14.1'

__all__ = ('Factory', 'Generator', 'Faker')
54 changes: 51 additions & 3 deletions faker/providers/python/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,58 @@ def pyint(self, min_value=0, max_value=9999, step=1):

def pydecimal(self, left_digits=None, right_digits=None, positive=False,
min_value=None, max_value=None):
if left_digits is not None and left_digits < 0:
raise ValueError(
'A decimal number cannot have less than 0 digits in its '
'integer part')
if right_digits is not None and right_digits < 0:
raise ValueError(
'A decimal number cannot have less than 0 digits in its '
'fractional part')
if (left_digits is not None and left_digits == 0) and (right_digits is not None and right_digits == 0):
raise ValueError(
'A decimal number cannot have 0 digits in total')
if None not in (min_value, max_value) and min_value > max_value:
raise ValueError('Min value cannot be greater than max value')
if None not in (min_value, max_value) and min_value == max_value:
raise ValueError('Min and max value cannot be the same')
if positive and min_value is not None and min_value <= 0:
raise ValueError(
'Cannot combine positive=True with negative or zero min_value')
if left_digits is not None and max_value and math.ceil(math.log10(abs(max_value))) > left_digits:
raise ValueError('Max value must fit within left digits')
if left_digits is not None and min_value and math.ceil(math.log10(abs(min_value))) > left_digits:
raise ValueError('Min value must fit within left digits')

# if either left or right digits are not specified we randomly choose a length
max_random_digits = 100
minimum_left_digits = len(str(min_value)) if min_value is not None else 1
if left_digits is None and right_digits is None:
right_digits = self.random_int(1, max_random_digits)
left_digits = self.random_int(minimum_left_digits, max_random_digits)
if left_digits is not None and right_digits is None:
right_digits = self.random_int(1, max_random_digits)
if left_digits is None and right_digits is not None:
left_digits = self.random_int(minimum_left_digits, max_random_digits)

float_ = self.pyfloat(
left_digits, right_digits, positive, min_value, max_value)
return Decimal(str(float_))
sign = ''
left_number = ''.join([str(self.random_digit()) for i in range(0, left_digits)]) or '0'
if right_digits is not None:
right_number = ''.join([str(self.random_digit()) for i in range(0, right_digits)])
else:
right_number = ''
sign = '+' if positive else self.random_element(('+', '-'))

result = Decimal(f'{sign}{left_number}.{right_number}')

# Because the random result might have the same number of decimals as max_value the random number
# might be above max_value or below min_value
if max_value is not None and result > max_value:
result = max_value
if min_value is not None and result < min_value:
result = min_value

return result

def pytuple(self, nb_elements=10, variable_nb_elements=True, value_types=None, *allowed_types):
return tuple(
Expand Down
124 changes: 124 additions & 0 deletions tests/providers/test_python.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import decimal
import sys
import unittest
import warnings
Expand Down Expand Up @@ -215,6 +216,129 @@ def test_positive_doesnt_return_zero(self):
self.assertGreater(result, 0)


class TestPydecimal(unittest.TestCase):
def setUp(self):
self.fake = Faker()
Faker.seed(0)

def test_pydecimal(self):
result = self.fake.pydecimal()

self.assertIsInstance(result, decimal.Decimal)

def test_left_digits(self):
expected_left_digits = 10

result = self.fake.pydecimal(left_digits=expected_left_digits)

left_digits = len(str(abs(int(result))))
self.assertGreaterEqual(expected_left_digits, left_digits)

def test_right_digits(self):
expected_right_digits = 10

result = self.fake.pydecimal(right_digits=expected_right_digits)

right_digits = len(str(result).split('.')[1])
self.assertGreaterEqual(expected_right_digits, right_digits)

def test_positive(self):
result = self.fake.pydecimal(positive=True)

self.assertGreater(result, 0)
abs_result = -result if result < 0 else result # abs() result returns scientific notation
self.assertEqual(result, abs_result)

def test_min_value(self):
min_values = (0, 10, -1000, 1000, 999999)

for min_value in min_values:
result = self.fake.pydecimal(min_value=min_value)
self.assertGreaterEqual(result, min_value)

def test_min_value_and_left_digits(self):
"""
Combining the min_value and left_digits keyword arguments produces
numbers that obey both of those constraints.
"""

result = self.fake.pydecimal(left_digits=1, min_value=0)
self.assertLess(result, 10)
self.assertGreaterEqual(result, 0)

def test_max_value(self):
max_values = (0, 10, -1000, 1000, 999999)

for max_value in max_values:
result = self.fake.pydecimal(max_value=max_value)
self.assertLessEqual(result, max_value)

def test_max_value_zero_and_left_digits(self):
"""
Combining the max_value and left_digits keyword arguments produces
numbers that obey both of those constraints.
"""

result = self.fake.pydecimal(left_digits=2, max_value=0)
self.assertLessEqual(result, 0)
self.assertGreater(result, -100)

def test_max_value_should_be_greater_than_min_value(self):
"""
An exception should be raised if min_value is greater than max_value
"""
expected_message = 'Min value cannot be greater than max value'
with self.assertRaises(ValueError) as raises:
self.fake.pydecimal(min_value=100, max_value=0)

message = str(raises.exception)
self.assertEqual(message, expected_message)

def test_max_value_and_positive(self):
"""
Combining the max_value and positive keyword arguments produces
numbers that obey both of those constraints.
"""

result = self.fake.pydecimal(positive=True, max_value=100)
self.assertLessEqual(result, 100)
self.assertGreater(result, 0)

def test_max_and_min_value_negative(self):
"""
Combining the max_value and min_value keyword arguments with
negative values for each produces numbers that obey both of
those constraints.
"""

result = self.fake.pydecimal(max_value=-100, min_value=-200)
self.assertLessEqual(result, -100)
self.assertGreaterEqual(result, -200)

def test_positive_and_min_value_incompatible(self):
"""
An exception should be raised if positive=True is set, but
a negative min_value is provided.
"""

expected_message = (
"Cannot combine positive=True with negative or zero min_value"
)
with self.assertRaises(ValueError) as raises:
self.fake.pydecimal(min_value=-100, positive=True)

message = str(raises.exception)
self.assertEqual(message, expected_message)

def test_positive_doesnt_return_zero(self):
"""
Choose the right_digits and max_value so it's guaranteed to return zero,
then watch as it doesn't because positive=True
"""
result = self.fake.pydecimal(positive=True, right_digits=0, max_value=1)
self.assertGreater(result, 0)


class TestPystrFormat(unittest.TestCase):

def setUp(self):
Expand Down
8 changes: 8 additions & 0 deletions tests/test_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,14 @@ def test_negative_pyfloat(self):
assert any(fake.pyfloat(left_digits=0, positive=False) < 0 for _ in range(100))
assert any(fake.pydecimal(left_digits=0, positive=False) < 0 for _ in range(100))

def test_arbitrary_digits_pydecimal(self):
# tests for https://github.com/joke2k/faker/issues/1462
fake = Faker()
assert any(
len(str(fake.pydecimal(left_digits=sys.float_info.dig + i))) > sys.float_info.dig for i in range(100)
)
assert any(len(str(fake.pydecimal())) > sys.float_info.dig for _ in range(100))

def test_pyfloat_empty_range_error(self):
# tests for https://github.com/joke2k/faker/issues/1048
fake = Faker()
Expand Down