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

Support for short-circuit evaluation #17253

Closed
mmzeynalli opened this issue May 16, 2024 · 8 comments
Closed

Support for short-circuit evaluation #17253

mmzeynalli opened this issue May 16, 2024 · 8 comments
Labels
bug mypy got something wrong

Comments

@mmzeynalli
Copy link

Bug Report

Mypy fails to evaluate variable type in short-circuit evaluation.

To Reproduce

class PortfolioQueryBuilder:
    def __init__(self, user_username: Optional[str] = None) :
        self.user_username = user_username

    def build_query(self):
        '''rest of the code/handling here'''

class Token:
    def __init__(self, username: str):
        self.username = username

auth = Token('username')
# auth = None
builder = PortfolioQueryBuilder(user_username=auth and auth.username)

Expected Behavior

It should not give an error, as line auth and auth.username always returns either None or username string, because of short-circuit evaluation.

Actual Behavior

When I run mypy, I get this error:

error: Argument "user_username" to "PortfolioQueryBuilder" has incompatible type "Token | str | None"; expected "str | None"  [arg-type]

Your Environment

  • Mypy version used: mypy 1.9.0 (compiled: yes)
  • Mypy command-line flags: None
  • Mypy configuration options from mypy.ini (and other config files):
[tool.mypy]
plugins = ["sqlalchemy.ext.mypy.plugin", "pydantic.mypy"]
exclude = [
    "^admin\\.py$"
]
  • Python version used: 3.11.5
@mmzeynalli mmzeynalli added the bug mypy got something wrong label May 16, 2024
@JelleZijlstra
Copy link
Member

Mypy is technically correct here; it could be a subclass of Token that overrides __bool__ to return False.

A bit arcane but I think this sort of pattern is better avoided in type-safe code.

@erictraut
Copy link

Consistent with Jelle's insight, the behavior changes if you mark the Token class as @final.

@JelleZijlstra
Copy link
Member

Indeed, thanks @erictraut: https://mypy-play.net/?mypy=latest&python=3.10&gist=c808a9a03bd113724835d66cccdd062e. With that I don't think there's anything left to do here; mypy's behavior is correct and @final can be used to work around this behavior if it's a problem.

@JelleZijlstra JelleZijlstra closed this as not planned Won't fix, can't repro, duplicate, stale May 16, 2024
@mmzeynalli
Copy link
Author

mmzeynalli commented May 16, 2024

I see, @final did indeed work. Thanks

@mmzeynalli
Copy link
Author

mmzeynalli commented May 16, 2024

Okay, I have a little different circumstance, which gives the same error:

from typing import final

from fastapi import Depends
from pydantic import BaseModel


@final
class Token(BaseModel):
    username: str
    exp: Optional[int] = None
    user_type: int


OptionalAuthUser = Annotated[Token | None, Depends(get_optional_user_access)]


@router.get(
    '',
    summary='Get portfolio list',
    status_code=status.HTTP_200_OK,
)
async def get_all_profiles_portfolios(
    db: DbDependency,
    auth: OptionalAuthUser,
):
    builder = PortfolioQueryBuilder(user_username=auth and auth.username)
    '''rest of the code'''

How should I act here, considering Token if wrapped into Annotated?

@JelleZijlstra
Copy link
Member

You use TokenPayload but don't show that definition in your example. I would recommend rewriting as auth.username if auth is not None else None.

@mmzeynalli
Copy link
Author

mmzeynalli commented May 16, 2024

My bad, fixed it. I did that before, but then I changed the code to as shown, however mypy complains. It does not hinder my work at all, but I would still like to know how to handle this, if I were to use short-circuit.

@KotlinIsland
Copy link
Contributor

@mmzeynalli I don't see any error, maybe you could provide a more complete example?

from typing import final, Optional, Annotated

from fastapi import Depends
from pydantic import BaseModel


@final
class Token(BaseModel):
    username: str
    exp: Optional[int] = None
    user_type: int

def get_optional_user_access(): ...

OptionalAuthUser = Annotated[Token | None, Depends(get_optional_user_access)]


async def get_all_profiles_portfolios(
        auth: OptionalAuthUser,
):
    builder: str | None = auth and auth.username
    '''rest of the code'''
> mypy test.py
Success: no issues found in 1 source file

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

No branches or pull requests

4 participants