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

conint (false?) flagged as a problem in PROBLEMS tab #4675

Closed
thomasheadrapson opened this issue Aug 1, 2023 · 7 comments
Closed

conint (false?) flagged as a problem in PROBLEMS tab #4675

thomasheadrapson opened this issue Aug 1, 2023 · 7 comments
Assignees
Labels
needs repro Issue has not been reproduced yet

Comments

@thomasheadrapson
Copy link

Environment data

  • Language Server version: 2023.7.40
  • OS and version: linux x64
  • Python version (and distribution if applicable, e.g. Anaconda): Anaconda
  • python.analysis.indexing: true
  • python.analysis.typeCheckingMode: basic

Code Snippet

class Rating(BaseModel):
"""
new_rating by userid from a movieid

"""
ratingid: Optional[int] = None
userid: int
movieid: int
rating: conint(ge=0,le=5)
XXX

Repro Steps

  1. XXX

Expected behavior

conint recognised and correctly assessed

XXX

Actual behavior

Flagged as problem in PROBLEMS tab with message
"
Call expression not allowed in type expression Pylance(reportGeneralTypeIssues)
"
and "conint(ge=0,le=5)" as shown in code snippet is underlined in red.

XXX

Logs

XXX
@github-actions github-actions bot added the needs repro Issue has not been reproduced yet label Aug 1, 2023
@erictraut
Copy link
Contributor

You're using a call expression (conint(ge=0, le=5)) within a type annotation, which isn't permitted in the Python static type system.

If you provide more context about what you're attempting to do, we may be able to give you some additional tips.

@bschnurr
Copy link
Member

bschnurr commented Aug 1, 2023

I found this example. looks like conint comes from pydantic
https://stravalib.readthedocs.io/en/v1.3.1/_modules/stravalib/strava_model.html#

@erictraut
Copy link
Contributor

That example is invalid. Call expression forms are not allowed in type expressions (annotations).

@bschnurr bschnurr closed this as completed Aug 1, 2023
@sebastianvitterso
Copy link

... which isn't permitted in the Python static type system.

You say it "isn't permitted", but the code surely runs, and for libraries such as Pydantic, it is very helpful.

In what way can it "not be permitted", if it works? It's like saying you can't assign a number to a variable that used to contain a string - surely some people don't like that, but Python will allow it and the code will run.

Allowing e.g. conint()-calls as types will help developers (especially those using Pydantic) write short, understandable code, without having to resort to writing manual, repetative validation for stuff that can simply be written as rating: conint(ge=0,le=5)

@erictraut
Copy link
Contributor

The purpose of static type checking is to help you find bugs ahead of time and to write robust code that will not break over time as you update libraries, refactor your code, etc. If you find that static type checking is not sufficiently beneficial, you have the option of disabling it (by setting python.analysis.typeCheckingMode to "off"). It's entirely optional in Python. If you find that static type checking is useful, then you need to use the static type system properly. That means, among other things, that type annotations must conform to expression types that are allowed by static type checkers. It's completely up to you.

@sebastianvitterso
Copy link

sebastianvitterso commented Sep 1, 2023

I realize that, but I believe the point of functions as types (such as conint, constr, confloat in Pydantic) is to simplify the combination of static type checking (to statically check that you're passing an int) and constraint validation (to dynamically check that the int is ge=0 and le=5).

Although these validation operations are wildly different in operation, they conceptually check similar things, and therefore it makes sense (and is quite ergonomic) to a developer to have them in the same place.


Interestingly, the definition of the conint function is as follows:

def conint(
    *,
    strict: bool | None = None,
    gt: int | None = None,
    ge: int | None = None,
    lt: int | None = None,
    le: int | None = None,
    multiple_of: int | None = None,
) -> type[int]:
    """A wrapper around `int` that allows for additional constraints.

    Args:
        strict: Whether to validate the integer in strict mode. Defaults to `None`.
        gt: The value must be greater than this.
        ge: The value must be greater than or equal to this.
        lt: The value must be less than this.
        le: The value must be less than or equal to this.
        multiple_of: The value must be a multiple of this.

    Returns:
        The wrapped integer type.
    """
    return Annotated[  # type: ignore[return-value]
        int,
        Strict(strict) if strict is not None else None,
        annotated_types.Interval(gt=gt, ge=ge, lt=lt, le=le),
        annotated_types.MultipleOf(multiple_of) if multiple_of is not None else None,
    ]

My argument with this is that it returns a simple type expression, so why can't a call to a function that returns a type expression be used as a type expression?

I realize that I might have to raise this question on the Pyright repository instead, but I feel like the restriction of just saying "you can't have function calls there" seems a bit arbitrary.

@erictraut
Copy link
Contributor

erictraut commented Sep 1, 2023

I'm the primary author of pyright, so there's no need to raise the question in the pyright repo. You'll get the same answer there. The restriction on call expressions isn't specific to pyright. It's something that none of the Python type checkers (including mypy, pyre, and pytype) allow.

Type expressions need to be fast to evaluate and non-ambiguous. Call expressions are slow to evaluate and can evaluate differently based on subtle differences between type checkers (e.g. due to overload rules).

PEP 484, which introduced the original aspects of static typing to Python, indicates that "Annotations should be kept simple or static analysis tools may not be able to interpret the values." This statement is somewhat ambiguous, but it was later clarified and agreed upon by all of the type checker maintainers. Call expressions are not allowed in type expressions.

Like I said, if you don't want to use static type checking, you can disable it. If you want to use static type checking, you need to abide by its rules.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs repro Issue has not been reproduced yet
Projects
None yet
Development

No branches or pull requests

4 participants