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

pydecimal() - min_value and max_value should support Decimal, along with float #2041

Open
sshishov opened this issue May 5, 2024 · 12 comments
Assignees

Comments

@sshishov
Copy link
Contributor

sshishov commented May 5, 2024

  • Faker version: 25.0.1
  • OS: MacOS 14.4.1 (23E224)

After migrating to version 25+, profiding default value as Decimal for min or max values causes violation

Steps to reproduce

amount=fake.pydecimal(min_value=Decimal(1), max_value=Decimal(100000)),

Expected behavior

The should not be any error observed. The rationale - a lot of software using Decimal from the box for all calculations, in the constants etc to rely on precise double calculation. Therefore I think that it should support Decimal from the box, especially if it is returning Decimal itself.

Actual behavior

Produces this mypy error:

 error: Argument "min_value" to "pydecimal" of "Faker" has incompatible type "Decimal"; expected "float | None"  [arg-type]
@sshishov sshishov changed the title pydecimal - min_value and max_value should support Decimal, along with float pydecimal() - min_value and max_value should support Decimal, along with float May 5, 2024
@stefan6419846
Copy link
Contributor

This is a typing issue, no "real" bug. Feel free to provide a PR to update the corresponding type hint and regenerate the typing stubs.

@sshishov
Copy link
Contributor Author

sshishov commented May 5, 2024

@stefan6419846 how I can report typing issues? Because we are havily using faker, and we are requiring strict typing, that's why I have noticed these errors...

If you will give me a way to report typing issues, then I will report there.

@stefan6419846
Copy link
Contributor

It is indeed correct to report this here, but your initial report sounded like an implementation bug and not a type hint issue.

@parsariyahi
Copy link
Contributor

If we want to fix that, we can add more annotation in code

    def pydecimal(
        self,
        left_digits: Optional[int] = None,
        right_digits: Optional[int] = None,
        positive: bool = False,
        min_value: Optional[Union[float, int, Decimal]] = None,
        max_value: Optional[Union[float, int, Decimal]] = None,
    ) -> Decimal:

but I think a problem is, we can have another types of numbers that are subclass of int or something like this,
how we can handle that in here?

@sshishov
Copy link
Contributor Author

Hi @parsariyahi , any subclass should be okay, I guess. We need to check though...

@parsariyahi
Copy link
Contributor

parsariyahi commented May 28, 2024

After some research, I found something, the built-in numbers module, this module has a class Number which is a abstract base class for all numbers I think.
you can read it here: https://docs.python.org/3/library/numbers.html
as the docs said:

The numbers module (PEP 3141) defines a hierarchy of numeric abstract base classes which progressively define more operations. None of the types defined in this module are intended to be instantiated.

I think it's fit the needs for this annotation

@fcurella
Copy link
Collaborator

fcurella commented Jun 3, 2024

@parsariyahi using numbers.Number seems like the right solution here. Do y ou have time to prepare a Pull Request?

@parsariyahi
Copy link
Contributor

parsariyahi commented Jun 3, 2024

@fcurella Yes sure, you can asign this issue to me, I will prepare a PR

@fcurella
Copy link
Collaborator

turns out, Decimal is not part of numbers.Number.

I've tried using Union[Decimal, int, float], but looks like abs() doesn't accept Decimal, so the best I can do is to define a new type BasicNumber = [int, float]

@jamescooke
Copy link

jamescooke commented Sep 17, 2024

@fcurella Could you share a bit more about what you wrote above?

but looks like abs() doesn't accept Decimal,

abs() says:

The argument may be an integer, a floating-point number, or an object implementing __abs__().

And we have:

In [12]: Decimal.__abs__?
Signature:      Decimal.__abs__(self, /)
Call signature: Decimal.__abs__(*args, **kwargs)
Type:           wrapper_descriptor
String form:    <slot wrapper '__abs__' of 'decimal.Decimal' objects>
Docstring:      abs(self)

Plus it's clearly possible to call abs(Decimal(-1)) and get a reasonable result.

So I'm confused why you'd say it's not supported - could you help me understand 🙏🏻

@sshishov
Copy link
Contributor Author

sshishov commented Nov 19, 2024

@fcurella the decimal.Decimal is the part of number.Number as far as I can tell.

You can even test it like this (Python 3.11.10):

In [1]: import numbers

In [2]: import decimal

In [3]: issubclass(decimal.Decimal, numbers.Number)
Out[3]: True

In [4]: isinstance(decimal.Decimal(100), numbers.Number)
Out[4]: True

I have tried the new version and all the complains disappeared for me. Am I missing something?

@fcurella
Copy link
Collaborator

The PEP for Decimal states:

After consultation with its authors it has been decided that the Decimal type should not at this time be made part of the numeric tower.

I've tried replacing the type with numbers.Number, but mypy v1.13.0 on Python 3.10 errors out:

$ mypy --install-types --non-interactive --config mypy.ini faker
faker/providers/python/__init__.py:299:64: error: Unsupported left operand type for > ("Number")  [operator]
            if min_value is not None and max_value is not None and min_value > max_value:
                                                                   ^~~~~~~~~~~~~~~~~~~~~
faker/providers/python/__init__.py:303:51: error: Unsupported operand types for >= ("int" and "Number")  [operator]
            if positive and min_value is not None and min_value <= 0:
                                                      ^
faker/providers/python/__init__.py:305:79: error: Argument 1 to "abs" has incompatible type "Number"; expected "SupportsAbs[Never]"  [arg-type]
            if left_digits is not None and max_value and math.ceil(math.log10(abs(max_value))) > left_digits:
                                                                                  ^~~~~~~~~
faker/providers/python/__init__.py:307:79: error: Argument 1 to "abs" has incompatible type "Number"; expected "SupportsAbs[Never]"  [arg-type]
            if left_digits is not None and min_value and math.ceil(math.log10(abs(min_value))) > left_digits:
                                                                                  ^~~~~~~~~
faker/providers/python/__init__.py:314:38: error: Argument 1 to "abs" has incompatible type "Number | int"; expected "SupportsAbs[int]"  [arg-type]
                math.ceil(math.log10(abs(min_value or 1))),
                                         ^~~~~~~~~~~~~~
faker/providers/python/__init__.py:315:38: error: Argument 1 to "abs" has incompatible type "Number | int"; expected "SupportsAbs[int]"  [arg-type]
                math.ceil(math.log10(abs(max_value or 1))),
                                         ^~~~~~~~~~~~~~
faker/providers/python/__init__.py:319:38: error: Unsupported operand types for <= ("int" and "Number")  [operator]
            if min_value is not None and min_value >= 0:
                                         ^
faker/providers/python/__init__.py:321:40: error: Unsupported operand types for >= ("int" and "Number")  [operator]
            elif max_value is not None and max_value <= 0:
                                           ^
faker/providers/python/__init__.py:331:55: error: Value of type variable "SupportsRichComparisonT" of "max" cannot be "Number | int"  [type-var]
                    left_number = str(self.random_int(int(max(min_value or 0, 0)), int(max_value)))
                                                          ^~~~~~~~~~~~~~~~~~~~~~
faker/providers/python/__init__.py:331:55: error: Argument 1 to "int" has incompatible type "Number | int"; expected "str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc"  [arg-type]
                    left_number = str(self.random_int(int(max(min_value or 0, 0)), int(max_value)))
                                                          ^~~~~~~~~~~~~~~~~~~~~~
faker/providers/python/__init__.py:331:80: error: No overload variant of "int" matches argument type "Number"  [call-overload]
                    left_number = str(self.random_int(int(max(min_value or 0, 0)), int(max_value)))
                                                                                   ^~~~~~~~~~~~~~
faker/providers/python/__init__.py:331:80: note: Possible overload variants:
faker/providers/python/__init__.py:331:80: note:     def __new__(cls, str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = ..., /) -> int
faker/providers/python/__init__.py:331:80: note:     def __new__(cls, str | bytes | bytearray, /, base: SupportsIndex) -> int
faker/providers/python/__init__.py:333:56: error: Value of type variable "SupportsRichComparisonT" of "max" cannot be "Number | int"  [type-var]
                    min_left_digits = math.ceil(math.log10(max(min_value or 1, 1)))
                                                           ^~~~~~~~~~~~~~~~~~~~~~
faker/providers/python/__init__.py:333:56: error: Argument 1 to "log10" has incompatible type "Number | int"; expected "SupportsFloat | SupportsIndex"  [arg-type]
                    min_left_digits = math.ceil(math.log10(max(min_value or 1, 1)))
                                                           ^~~~~~~~~~~~~~~~~~~~~~
faker/providers/python/__init__.py:339:59: error: Value of type variable "SupportsRichComparisonT" of "min" cannot be "Number | int"  [type-var]
                    left_number = str(self.random_int(int(abs(min(max_value or 0, 0))), int(abs(min_value))))
                                                              ^~~~~~~~~~~~~~~~~~~~~~
faker/providers/python/__init__.py:339:59: error: Argument 1 to "abs" has incompatible type "Number | int"; expected "SupportsAbs[int]"  [arg-type]
                    left_number = str(self.random_int(int(abs(min(max_value or 0, 0))), int(abs(min_value))))
                                                              ^~~~~~~~~~~~~~~~~~~~~~
faker/providers/python/__init__.py:339:93: error: Argument 1 to "abs" has incompatible type "Number"; expected "SupportsAbs[Never]"  [arg-type]
                    left_number = str(self.random_int(int(abs(min(max_value or 0, 0))), int(abs(min_value))))
                                                                                                ^~~~~~~~~
faker/providers/python/__init__.py:341:60: error: Value of type variable "SupportsRichComparisonT" of "min" cannot be "Number | int"  [type-var]
                    min_left_digits = math.ceil(math.log10(abs(min(max_value or 1, 1))))
                                                               ^~~~~~~~~~~~~~~~~~~~~~
faker/providers/python/__init__.py:341:60: error: Argument 1 to "abs" has incompatible type "Number | int"; expected "SupportsAbs[int]"  [arg-type]
                    min_left_digits = math.ceil(math.log10(abs(min(max_value or 1, 1))))
                                                               ^~~~~~~~~~~~~~~~~~~~~~
faker/providers/python/__init__.py:355:47: error: Unsupported operand types for > ("Decimal" and "Number")  [operator]
            if max_value is not None and result > max_value:
                                                  ^~~~~~~~~
faker/providers/python/__init__.py:357:47: error: Unsupported operand types for < ("Decimal" and "Number")  [operator]
            if min_value is not None and result < min_value:
                                                  ^~~~~~~~~
Found 20 errors in 1 file (checked 672 source files)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants