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

pyright does not type-guards to bottle.FormsDict #4350

Closed
ilrico opened this issue Dec 17, 2022 · 5 comments
Closed

pyright does not type-guards to bottle.FormsDict #4350

ilrico opened this issue Dec 17, 2022 · 5 comments
Labels
as designed Not a bug, working as intended

Comments

@ilrico
Copy link

ilrico commented Dec 17, 2022

python 3.11.0, pyright 1.1284

from pydantic import BaseModel
from bottle import FormsDict

class A(BaseModel):
    def set(self, data=None):
        if isinstance(data, FormsDict):
            data_as_dict = data.dict     # pyright error: "dict" is not a known member of "None"
        for k, v in data.items():        # pyright error: "items" is not a known member of "None"
            pass

Second error is logical but first does not (at least to me, even after pyright's type-concept)

If we move loop inside type-safe branch:

from pydantic import BaseModel
from bottle import FormsDict

class A(BaseModel):
    def set(self, data=None):
        if isinstance(data, FormsDict):
            data_as_dict = data.dict     # pyright error: "dict" is not a known member of "None"
            for k, v in data.items():    # no error
                pass

The .items() error is cleared, as expected, so seems that pyright somewhat takes into account isinstance

If we hint function signature as follow:

from pydantic import BaseModel
from bottle import FormsDict

class A(BaseModel):
    def set(self, data:FormsDict=None):    # no error
        if isinstance(data, FormsDict):
            data_as_dict = data.dict       # no error
        for k, v in data.items():          # no error
            pass

Here, all errors are cleared, but I would expect 2 errors: one in signature (should be FormsDict | None), and the most obvious with .items() call.

@erictraut
Copy link
Collaborator

I'm not able to repro the first of the two errors you're seeing. Which version of the bottle library are you using? I just installed the latest version (0.12.23). I'll note that this library doesn't have type information. Are you using type stubs for it? Or perhaps you've enabled pyright's useLibraryCodeForTypes feature to infer type information from the code?

Do you have any other non-default settings configured for pyright?

@erictraut
Copy link
Collaborator

Ah, I am able to repro the problem if useLibraryCodeForTypes is disabled (as it is by default). In this case, the type of FormsDict is Unknown, so type narrowing cannot be applied.

If you want to use static type checking with the bottle library, you need to either find or create a type stub that provides type information for it or enable useLibraryCodeForTypes to tell pyright that it should infer types from the available source code.

This isn't a bug, this is the expected behavior.

@erictraut erictraut added the as designed Not a bug, working as intended label Dec 17, 2022
@erictraut
Copy link
Collaborator

If this library is important to you, I recommend contacting its maintainers and asking them to add inlined type information and mark the library as typed by including a "py.typed" marker. For more details, they can refer to this documentation.

@ilrico
Copy link
Author

ilrico commented Dec 17, 2022

@erictraut thx for your insights, I supposed that indeed pyright would infer types from lib code.
I also imagine there are good reasons to not enable it by default.

Now, with this flag enabled:
I don't have errors anymore without hint in signature, which is correct.
I have an error on signature with data: FormsDict=None which also seems logic.
And with hint like data: Optional[FormsDict, None]=None, no error, which is also correct.

But... if we shadow data without hint in the shadowing we have an error

from pydantic import BaseModel
from bottle import FormsDict

class A(BaseModel):
    def set(self, data: Optional[FormsDict]=None):    # no error
        if isinstance(data, FormsDict):
            data = data.dict       # error

Again, we would expect new data variable to have its type infered by FormsDict.dict
Pyright still assumes that data type must be FormsDict.
With type-hint, data: dict = data.dict error is cleared.
One might argue that shadowing variable is a bad practice, which is not totally unfounded (and Pyright warns us about that shadowing... but only if type-hints are present on shadowning), but still, here type should be infered IMHO.

@erictraut
Copy link
Collaborator

erictraut commented Dec 17, 2022

In this case, you have declared that the symbol data accepts only types FormsDict or None. Then you've attempted to assign a value to it that is incompatible with those types. It's the job of a type checker to validate assignments and ensure that type declarations are not violated, so pyright is correct in emitting an error here. For more details about type concepts, refer to this documentation. If you have additional questions about how static type checking works in Python, feel free to post questions in the discussion forum.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
as designed Not a bug, working as intended
Projects
None yet
Development

No branches or pull requests

2 participants