You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
546 lines
21 KiB
546 lines
21 KiB
import math
|
|
import string
|
|
import sys
|
|
import warnings
|
|
|
|
from decimal import Decimal
|
|
from enum import Enum
|
|
from typing import Any, Dict, Iterable, Iterator, List, Optional, Set, Tuple, Type, TypeVar, Union, cast, no_type_check
|
|
|
|
from ...exceptions import BaseFakerException
|
|
from .. import BaseProvider, ElementsType
|
|
|
|
TypesNames = List[str]
|
|
TypesSpec = Union[List[Type], Tuple[Type, ...]]
|
|
TEnum = TypeVar("TEnum", bound=Enum)
|
|
|
|
|
|
class EmptyEnumException(BaseFakerException):
|
|
pass
|
|
|
|
|
|
class Provider(BaseProvider):
|
|
default_value_types: ElementsType[str] = (
|
|
"str",
|
|
"str",
|
|
"str",
|
|
"str",
|
|
"float",
|
|
"int",
|
|
"int",
|
|
"decimal",
|
|
"date_time",
|
|
"uri",
|
|
"email",
|
|
)
|
|
|
|
def _check_signature(self, value_types: Optional[TypesSpec], allowed_types: Optional[TypesSpec]) -> TypesSpec:
|
|
if value_types is not None and not isinstance(value_types, (list, tuple)):
|
|
value_types = (value_types,)
|
|
warnings.warn(
|
|
"Passing `value_types` as positional arguments is going to be "
|
|
"deprecated. Pass them as a list or tuple instead.",
|
|
PendingDeprecationWarning,
|
|
)
|
|
if allowed_types is not None and not isinstance(allowed_types, (list, tuple)):
|
|
allowed_types = (allowed_types,)
|
|
warnings.warn(
|
|
"Passing `allowed_types` as positional arguments is going to be "
|
|
"deprecated. Pass them as a list or tuple instead.",
|
|
PendingDeprecationWarning,
|
|
)
|
|
if value_types is None:
|
|
value_types = ()
|
|
if allowed_types is None:
|
|
allowed_types = ()
|
|
return tuple(value_types) + tuple(allowed_types)
|
|
|
|
def pyobject(
|
|
self,
|
|
object_type: Optional[Type[Union[bool, str, float, int, tuple, set, list, Iterable, dict]]] = None,
|
|
) -> Optional[Union[bool, str, float, int, tuple, set, list, Iterable, dict]]:
|
|
"""
|
|
Generates a random object passing the type desired.
|
|
|
|
:object_type: the type of the object to generate.
|
|
:return: the random object generated.
|
|
:raises ValueError: if the object type passed is not supported
|
|
"""
|
|
if object_type is None:
|
|
return None
|
|
elif object_type == bool:
|
|
return self.pybool()
|
|
elif object_type == str:
|
|
return self.pystr()
|
|
elif object_type == float:
|
|
return self.pyfloat()
|
|
elif object_type == int:
|
|
return self.pyint()
|
|
elif object_type == tuple:
|
|
return self.pytuple()
|
|
elif object_type == set:
|
|
return self.pyset()
|
|
elif object_type == list:
|
|
return self.pylist()
|
|
elif object_type == Iterable:
|
|
return self.pyiterable()
|
|
elif object_type == dict:
|
|
return self.pydict()
|
|
else:
|
|
raise ValueError(f"Object type `{object_type}` is not supported by `pyobject` function")
|
|
|
|
def pybool(self, truth_probability: int = 50) -> bool:
|
|
"""
|
|
Generates a random boolean, optionally biased towards `True` or `False`.
|
|
|
|
:truth_probability: Probability of generating a `True` value. Must be between `0` and `100` inclusive'.
|
|
:return: Random boolean.
|
|
:raises ValueError: If invalid `truth_probability` is provided.
|
|
"""
|
|
if truth_probability < 0 or truth_probability > 100:
|
|
raise ValueError("Invalid `truth_probability` value: must be between `0` and `100` inclusive")
|
|
|
|
return self.random_int(1, 100) <= truth_probability
|
|
|
|
def pystr(
|
|
self,
|
|
min_chars: Optional[int] = None,
|
|
max_chars: int = 20,
|
|
prefix: str = "",
|
|
suffix: str = "",
|
|
) -> str:
|
|
"""
|
|
Generates a random string of upper and lowercase letters.
|
|
|
|
:param min_chars: minimum length of the random part.
|
|
:param max_chars: maximum length of the random part.
|
|
:param prefix: an optional prefix to prepend to the random string.
|
|
:param suffix: an optional suffix to append to the random string.
|
|
:return: Random of random length between min and max characters.
|
|
"""
|
|
if min_chars is None:
|
|
chars = "".join(self.random_letters(length=max_chars))
|
|
else:
|
|
assert max_chars >= min_chars, "Maximum length must be greater than or equal to minimum length"
|
|
chars = "".join(
|
|
self.random_letters(
|
|
length=self.generator.random.randint(min_chars, max_chars),
|
|
),
|
|
)
|
|
|
|
return prefix + chars + suffix
|
|
|
|
def pystr_format(
|
|
self,
|
|
string_format: str = "?#-###{{random_int}}{{random_letter}}",
|
|
letters: str = string.ascii_letters,
|
|
) -> str:
|
|
return self.bothify(self.generator.parse(string_format), letters=letters)
|
|
|
|
@no_type_check
|
|
def pyfloat(
|
|
self,
|
|
left_digits: Optional[int] = None,
|
|
right_digits: Optional[int] = None,
|
|
positive: Optional[bool] = None,
|
|
min_value: Optional[Union[float, int]] = None,
|
|
max_value: Optional[Union[float, int]] = None,
|
|
) -> float:
|
|
if left_digits is not None and left_digits < 0:
|
|
raise ValueError("A float number cannot have less than 0 digits in its " "integer part")
|
|
if right_digits is not None and right_digits < 0:
|
|
raise ValueError("A float number cannot have less than 0 digits in its " "fractional part")
|
|
if left_digits == 0 and right_digits == 0:
|
|
raise ValueError("A float number cannot have less than 0 digits in total")
|
|
if min_value is not None and max_value is not None:
|
|
if 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")
|
|
|
|
# Make sure at least either left or right is set
|
|
if left_digits is None and right_digits is None:
|
|
needed_left_digits = max(1, math.ceil(math.log10(max(abs(max_value or 1), abs(min_value or 1)))))
|
|
right_digits = self.random_int(1, sys.float_info.dig - needed_left_digits)
|
|
|
|
# If only one side is set, choose #digits for other side
|
|
if (left_digits is None) ^ (right_digits is None):
|
|
if left_digits is None:
|
|
left_digits = max(1, sys.float_info.dig - right_digits)
|
|
else:
|
|
right_digits = max(1, sys.float_info.dig - left_digits)
|
|
|
|
# Make sure we don't ask for too many digits!
|
|
if left_digits + right_digits > sys.float_info.dig:
|
|
raise ValueError(
|
|
f"Asking for too many digits ({left_digits} + {right_digits} == {left_digits + right_digits} > "
|
|
f"{sys.float_info.dig})",
|
|
)
|
|
|
|
sign = ""
|
|
if (min_value is not None) or (max_value is not None):
|
|
# Copy values to ensure we're not modifying the original values and thus going out of bounds
|
|
left_min_value = min_value
|
|
left_max_value = max_value
|
|
# Make sure left_digits still respected
|
|
if left_digits is not None:
|
|
if max_value is None:
|
|
left_max_value = 10**left_digits # minus smallest representable, adjusted later
|
|
if min_value is None:
|
|
left_min_value = -(10**left_digits) # plus smallest representable, adjusted later
|
|
|
|
if max_value is not None and max_value < 0:
|
|
left_max_value += 1 # as the random_int will be generated up to max_value - 1
|
|
if min_value is not None and min_value < 0:
|
|
left_min_value += 1 # as we then append digits after the left_number
|
|
left_number = self._safe_random_int(
|
|
left_min_value,
|
|
left_max_value,
|
|
positive,
|
|
)
|
|
else:
|
|
if positive is None:
|
|
sign = self.random_element(("+", "-"))
|
|
elif positive is True:
|
|
sign = "+"
|
|
else:
|
|
sign = "-"
|
|
|
|
left_number = self.random_number(left_digits)
|
|
|
|
result = float(f"{sign}{left_number}.{self.random_number(right_digits)}")
|
|
if positive and result == 0:
|
|
if right_digits:
|
|
result = float("0." + "0" * (right_digits - 1) + "1")
|
|
else:
|
|
result += sys.float_info.epsilon
|
|
|
|
if right_digits:
|
|
result = min(result, 10**left_digits - float(f'0.{"0" * (right_digits - 1)}1'))
|
|
result = max(result, -(10**left_digits + float(f'0.{"0" * (right_digits - 1)}1')))
|
|
else:
|
|
result = min(result, 10**left_digits - 1)
|
|
result = max(result, -(10**left_digits + 1))
|
|
|
|
# It's possible for the result to end up > than max_value or < than min_value
|
|
# When this happens we introduce some variance so we're not always the exactly the min_value or max_value.
|
|
# Which can happen a lot depending on the difference of the values.
|
|
# Ensure the variance is bound by the difference between the max and min
|
|
if max_value is not None:
|
|
if result > max_value:
|
|
result = result - (result - max_value + self.generator.random.uniform(0, max_value - min_value))
|
|
if min_value is not None:
|
|
if result < min_value:
|
|
result = result + (min_value - result + self.generator.random.uniform(0, max_value - min_value))
|
|
|
|
return result
|
|
|
|
def _safe_random_int(self, min_value: float, max_value: float, positive: bool) -> int:
|
|
orig_min_value = min_value
|
|
orig_max_value = max_value
|
|
|
|
if min_value is None:
|
|
min_value = max_value - self.random_int()
|
|
if max_value is None:
|
|
max_value = min_value + self.random_int()
|
|
if positive:
|
|
min_value = max(min_value, 0)
|
|
|
|
if min_value == max_value:
|
|
return self._safe_random_int(orig_min_value, orig_max_value, positive)
|
|
else:
|
|
min_value = int(min_value)
|
|
max_value = int(max_value - 1)
|
|
if max_value < min_value:
|
|
max_value += 1
|
|
return self.random_int(min_value, max_value)
|
|
|
|
def pyint(self, min_value: int = 0, max_value: int = 9999, step: int = 1) -> int:
|
|
return self.generator.random_int(min_value, max_value, step=step)
|
|
|
|
def _random_int_of_length(self, length: int) -> int:
|
|
"""Generate a random integer of a given length
|
|
|
|
If length is 0, so is the number. Otherwise the first digit must not be 0.
|
|
"""
|
|
|
|
if length < 0:
|
|
raise ValueError("Length must be a non-negative integer.")
|
|
elif length == 0:
|
|
return 0
|
|
else:
|
|
min_value = 10 ** (length - 1)
|
|
max_value = (10**length) - 1
|
|
return self.pyint(min_value=min_value, max_value=max_value)
|
|
|
|
def pydecimal(
|
|
self,
|
|
left_digits: Optional[int] = None,
|
|
right_digits: Optional[int] = None,
|
|
positive: bool = False,
|
|
min_value: Optional[float] = None,
|
|
max_value: Optional[float] = None,
|
|
) -> Decimal:
|
|
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 min_value is not None and max_value is not None and min_value > max_value:
|
|
raise ValueError("Min value cannot be greater than max value")
|
|
if min_value is not None and max_value is not None 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
|
|
# Because if min_value is bigger than 10**100
|
|
max_digits_from_value = max(
|
|
math.ceil(math.log10(abs(min_value or 1))),
|
|
math.ceil(math.log10(abs(max_value or 1))),
|
|
)
|
|
max_left_random_digits = max(max_random_digits, max_digits_from_value + 10)
|
|
|
|
if min_value is not None and min_value >= 0:
|
|
sign = "+"
|
|
elif max_value is not None and max_value <= 0:
|
|
sign = "-"
|
|
else:
|
|
sign = "+" if positive else self.random_element(("+", "-"))
|
|
|
|
if sign == "+":
|
|
if max_value is not None:
|
|
left_number = str(self.random_int(int(max(min_value or 0, 0)), int(max_value)))
|
|
else:
|
|
min_left_digits = math.ceil(math.log10(max(min_value or 1, 1)))
|
|
if left_digits is None:
|
|
left_digits = self.random_int(min_left_digits, max_left_random_digits)
|
|
left_number = str(self._random_int_of_length(left_digits))
|
|
else:
|
|
if min_value is not None:
|
|
left_number = str(self.random_int(int(max(max_value or 0, 0)), int(abs(min_value))))
|
|
else:
|
|
min_left_digits = math.ceil(math.log10(abs(min(max_value or 1, 1))))
|
|
if left_digits is None:
|
|
left_digits = self.random_int(min_left_digits, max_left_random_digits)
|
|
left_number = str(self._random_int_of_length(left_digits))
|
|
|
|
if right_digits is None:
|
|
right_digits = self.random_int(0, max_random_digits)
|
|
|
|
right_number = "".join([str(self.random_digit()) for i in range(0, right_digits)])
|
|
|
|
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 = Decimal(max_value)
|
|
if min_value is not None and result < min_value:
|
|
result = Decimal(min_value)
|
|
|
|
return result
|
|
|
|
def pytuple(
|
|
self,
|
|
nb_elements: int = 10,
|
|
variable_nb_elements: bool = True,
|
|
value_types: Optional[TypesSpec] = None,
|
|
allowed_types: Optional[TypesSpec] = None,
|
|
) -> Tuple[Any, ...]:
|
|
return tuple(
|
|
self._pyiterable(
|
|
nb_elements=nb_elements,
|
|
variable_nb_elements=variable_nb_elements,
|
|
value_types=value_types,
|
|
allowed_types=allowed_types,
|
|
)
|
|
)
|
|
|
|
def pyset(
|
|
self,
|
|
nb_elements: int = 10,
|
|
variable_nb_elements: bool = True,
|
|
value_types: Optional[TypesSpec] = None,
|
|
allowed_types: Optional[TypesSpec] = None,
|
|
) -> Set[Any]:
|
|
return set(
|
|
self._pyiterable(
|
|
nb_elements=nb_elements,
|
|
variable_nb_elements=variable_nb_elements,
|
|
value_types=value_types,
|
|
allowed_types=allowed_types,
|
|
)
|
|
)
|
|
|
|
def pylist(
|
|
self,
|
|
nb_elements: int = 10,
|
|
variable_nb_elements: bool = True,
|
|
value_types: Optional[TypesSpec] = None,
|
|
allowed_types: Optional[TypesSpec] = None,
|
|
) -> List[Any]:
|
|
return list(
|
|
self._pyiterable(
|
|
nb_elements=nb_elements,
|
|
variable_nb_elements=variable_nb_elements,
|
|
value_types=value_types,
|
|
allowed_types=allowed_types,
|
|
)
|
|
)
|
|
|
|
@no_type_check
|
|
def pyiterable(
|
|
self,
|
|
nb_elements: int = 10,
|
|
variable_nb_elements: bool = True,
|
|
value_types: Optional[TypesSpec] = None,
|
|
allowed_types: Optional[TypesSpec] = None,
|
|
) -> Iterable[Any]:
|
|
value_types: TypesSpec = self._check_signature(value_types, allowed_types)
|
|
return self.random_element([self.pylist, self.pytuple, self.pyset])(
|
|
nb_elements=nb_elements,
|
|
variable_nb_elements=variable_nb_elements,
|
|
value_types=value_types,
|
|
allowed_types=allowed_types,
|
|
)
|
|
|
|
def _random_type(self, type_list: List[str]) -> str:
|
|
value_type: str = self.random_element(type_list)
|
|
|
|
method_name = f"py{value_type}"
|
|
if hasattr(self, method_name):
|
|
value_type = method_name
|
|
|
|
return self.generator.format(value_type)
|
|
|
|
def _pyiterable(
|
|
self,
|
|
nb_elements: int = 10,
|
|
variable_nb_elements: bool = True,
|
|
value_types: Optional[TypesSpec] = None,
|
|
allowed_types: Optional[TypesSpec] = None,
|
|
) -> Iterator:
|
|
value_types: TypesSpec = self._check_signature(value_types, allowed_types)
|
|
|
|
value_types: TypesNames = [
|
|
t if isinstance(t, str) else getattr(t, "__name__", type(t).__name__).lower()
|
|
for t in value_types
|
|
# avoid recursion
|
|
if t not in ["iterable", "list", "tuple", "dict", "set"]
|
|
]
|
|
if not value_types:
|
|
value_types = self.default_value_types # type: ignore
|
|
|
|
if variable_nb_elements:
|
|
nb_elements = self.randomize_nb_elements(nb_elements, min=1)
|
|
|
|
for _ in range(nb_elements):
|
|
yield self._random_type(value_types)
|
|
|
|
def pydict(
|
|
self,
|
|
nb_elements: int = 10,
|
|
variable_nb_elements: bool = True,
|
|
value_types: Optional[TypesSpec] = None,
|
|
allowed_types: Optional[TypesSpec] = None,
|
|
) -> Dict[Any, Any]:
|
|
"""
|
|
Returns a dictionary.
|
|
|
|
:nb_elements: number of elements for dictionary
|
|
:variable_nb_elements: is use variable number of elements for dictionary
|
|
:value_types: type of dictionary values
|
|
"""
|
|
if variable_nb_elements:
|
|
nb_elements = self.randomize_nb_elements(nb_elements, min=1)
|
|
|
|
return dict(
|
|
zip(
|
|
self.generator.words(nb_elements, unique=True),
|
|
self._pyiterable(
|
|
nb_elements=nb_elements,
|
|
variable_nb_elements=False,
|
|
value_types=value_types,
|
|
allowed_types=allowed_types,
|
|
),
|
|
)
|
|
)
|
|
|
|
def pystruct(
|
|
self,
|
|
count: int = 10,
|
|
value_types: Optional[TypesSpec] = None,
|
|
allowed_types: Optional[TypesSpec] = None,
|
|
) -> Tuple[List, Dict, Dict]:
|
|
value_types: TypesSpec = self._check_signature(value_types, allowed_types)
|
|
|
|
value_types: TypesNames = [
|
|
t if isinstance(t, str) else getattr(t, "__name__", type(t).__name__).lower()
|
|
for t in value_types
|
|
# avoid recursion
|
|
if t != "struct"
|
|
]
|
|
if not value_types:
|
|
value_types = self.default_value_types # type: ignore
|
|
|
|
types = []
|
|
d = {}
|
|
nd = {}
|
|
for i in range(count):
|
|
d[self.generator.word()] = self._random_type(value_types)
|
|
types.append(self._random_type(value_types))
|
|
nd[self.generator.word()] = {
|
|
i: self._random_type(value_types),
|
|
i
|
|
+ 1: [
|
|
self._random_type(value_types),
|
|
self._random_type(value_types),
|
|
self._random_type(value_types),
|
|
],
|
|
i
|
|
+ 2: {
|
|
i: self._random_type(value_types),
|
|
i + 1: self._random_type(value_types),
|
|
i
|
|
+ 2: [
|
|
self._random_type(value_types),
|
|
self._random_type(value_types),
|
|
],
|
|
},
|
|
}
|
|
return types, d, nd
|
|
|
|
def enum(self, enum_cls: Type[TEnum]) -> TEnum:
|
|
"""
|
|
Returns a random enum of the provided input `Enum` type.
|
|
|
|
:param enum_cls: The `Enum` type to produce the value for.
|
|
:returns: A randomly selected enum value.
|
|
"""
|
|
|
|
if enum_cls is None:
|
|
raise ValueError("'enum_cls' cannot be None")
|
|
|
|
if not issubclass(enum_cls, Enum):
|
|
raise TypeError("'enum_cls' must be an Enum type")
|
|
|
|
members: List[TEnum] = list(cast(Iterable[TEnum], enum_cls))
|
|
|
|
if len(members) < 1:
|
|
raise EmptyEnumException(f"The provided Enum: '{enum_cls.__name__}' has no members.")
|
|
|
|
return self.random_element(members)
|