-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Description
Please provide more information to help us understand the issue:
mypy doesn't see an instance attribute is checked by a decorator, in an instance method.
- Please insert below the code you are checking with mypy,
or a mock-up repro if the source is private. We would appreciate
if you try to simplify your case to a minimal repro.
from abc import ABC
from enum import Enum, unique
from functools import wraps
from typing import Optional, Callable
@unique
class Role(Enum):
USER = 'user'
ADMIN = 'admin'
class User(object):
def __init__(self, email: str, password: str, role: Role):
self.email = email
self.password = password
self.role = role
class InitiatedCommand(ABC):
def __init__(self, initiator: Optional[User]):
self.initiator = initiator
self.require_initiator = True
class MissingInitiator(Exception):
pass
def check_initiator(func: Callable) -> Callable:
@wraps(func)
def wrapper(self: InitiatedCommand, *args, **kwargs):
if not self.initiator:
if self.require_initiator:
raise MissingInitiator
return
return func(self, *args, **kwargs)
return wrapper
class NotAllowed(Exception):
pass
class UpdateUser(InitiatedCommand):
def __init__(self, user: User, initiator: Optional[User] = None):
self.user = user
super().__init__(initiator)
self.email = None
self.password = None
self.role = None
@check_initiator
def update_role(self) -> None:
if self.initiator.role != Role.ADMIN:
raise NotAllowed
self.user.role = self.role
def execute(self):
self.update_role()
def test_initiator_is_required() -> None:
user = User(email='[email protected]', password='super-secret', role=Role.USER)
update_user = UpdateUser(user)
user.role = Role.ADMIN
try:
update_user.execute()
except MissingInitiator:
print('Yep, this is expected')
def test_initiator_disabled_on_purpose() -> None:
user = User(email='[email protected]', password='super-secret', role=Role.USER)
update_user = UpdateUser(user)
update_user.require_initiator = False
user.role = Role.ADMIN
update_user.execute()
print('User updated without initiator')
assert user.role == Role.ADMIN
- What is the actual behavior/output?
mypy output
root@8506a4af0701:/app# mypy tests/test_instance_decorator.py
tests/test_instance_decorator.py:56: error: Item "None" of "Optional[User]" has no attribute "role"
tests/test_instance_decorator.py:59: error: Incompatible types in assignment (expression has type "None", variable has type "Role")
Found 2 errors in 1 file (checked 1 source file)
pytest output:
root@8506a4af0701:/app# py.test tests/test_instance_decorator.py -s -vv
==================================================================== test session starts ====================================================================
platform linux -- Python 3.6.8, pytest-4.6.6, py-1.8.0, pluggy-0.13.0 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /anmo-app
plugins: cov-2.8.1
collected 2 items
tests/test_instance_decorator.py::test_initiator_is_required Yep, this is expected
PASSED
tests/test_instance_decorator.py::test_initiator_disabled_on_purpose User updated without initiator
PASSED
-
What is the behavior/output you expect?
I would expect mypy realized I'm checking that instance attribute in the decorator. -
What are the versions of mypy and Python you are using?
7.4.0 -
What are the mypy flags you are using? (For example --strict-optional)
None -
More context about what I'm trying to do
I'm building a small app that will be wrapped in a REST API. There areUser
s that have roles (user
andadmin
). The business logic says a user's role can be elevated only by anadmin
(so that's what's up with theinitiator
). However, I want to use this same command from CLI, where I can manually disable the initiator check.