import statement not respected in try/except statements
Description
You can find an simple example of the problem here, but a better example would be with the evdev module(where I found this), or other that have the same behavior.
To give a full explanation (just explaining the code in the library), I wanted to use the InputDevice class from evdev.device, which depends on a class named EventIO. This class is first imported from evdev.eventio_async, but if there is an ImportError, EventIO is imported from evdev.eventio.
The problem here, is that basedpyright only show completion for the attibutes and methods defined in evdev.device (for InputDevice) but not for its parent class EventIO.
(By the way, evdev.eventio_async's EventIO inherits evdev.eventio's EventIO; and the problem still happens even if it is an except: and not except ImportError:).
I think the problem comes from the import inside try/except block as using basedpyright on evdev.device shows that EventIO is unknow (despite being imported).
The playground example shows that the try: part of the block is not run because there is no reason for it to fail.
It seems like there is also the problem in pyright.
The playground example shows that the
try:part of the block is not run because there is no reason for it to fail.
this is because statically, there's no way for (based)pyright to know this. there's no way to tell the type checker that an exception definitely won't be raised, so it assumes that the except block is reachable.
ideally you'd be able to do something like this:
try:
from pathlib import Path as _PathlibPath
Path = _PathlibPath
except ImportError:
class _FallbackPath:
def __init__(self, val: str):
self.val: str = val
Path = _FallbackPath
class Foo(Path): ...
but this doesn't work either because it's essentially subtyping a union, which isn't allowed.
The problem here, is that basedpyright only show completion for the attibutes and methods defined in evdev.device (for InputDevice) but not for its parent class EventIO.
if you only care about completions and not type checking, maybe this could work for you?
from typing import Any
EventIO: Any
try:
from .eventio_async import EventIO as _AsyncEventIO
EventIO = _AsyncEventIO
except ImportError:
from .eventio import EventIO as _EventIO
EventIO = _EventIO
class InputDevice(EventIO):
...
this is because statically, there's no way for (based)pyright to know this. there's no way to tell the type checker that an exception definitely won't be raised, so it assumes that the
exceptblock is reachable.
I understand this, but when using the evdev module, the except part is not reached (even if we change the except ImportError into an except
if you only care about completions and not type checking, maybe this could work for you?
I would (even if I do care about type checking), if this was not part of a module. While I could change my installed version of the module, this would not be great
playing around with this some more, i can't seem to reproduce the issue you're describing with the EventIO class. completions from both the superclass and the subclass seem to work for me.
the minimal example you provided using Path on the other hand seems to behave differently because it's defining the class in the except block instead of importing it.
would you be able to provide an example of exactly how you're importing/using the EventIO class? either way this inconsistent behavior should be fixed, but i would just like to make sure we're on the same page so the fix actually addresses your use case.
Sure!
In a clean venv, using python 3.13.2, having done only pip install evdev, and changing the except ImportError: of .env/lib/python3.13/site-packages/evdev/device.py of line 9, to except:
from evdev import InputDevice
device = InputDevice(".")
device.leds() # Method from InputDevice, Warn about unused result
device.read_loop() # Method from EventIO, warning because 'Type of "read_loop" is unknown'
(And just to be sure, I also modified the system-wide installation, but it did not change anything)
hmm i guess it's because of these errors:
try:
from .eventio_async import EventIO
except ImportError:
from .eventio import EventIO
# Argument to class must be a base class (reportGeneralTypeIssues)
# Base class type is unknown, obscuring type of derived class (reportUntypedBaseClass)
class InputDevice(EventIO): ...
Except that there is still the error with
try:
from .eventio_async import EventIO
except:
from .eventio import EventIO
do you see a different error? i only see those two on the usage
No, this is the same errors, but if
it assumes that the
exceptblock is reachable.
, then the errors should not be there having except:
Because it can not know what type (if there is even any) error statically, I understand why there is an error with
try:
from .eventio_async import EventIO
except ImportError:
from .eventio import EventIO
However, there should be no errors with the following code, as "it assumes the except block is reachable", and so EvenIO is defined, and is a base class.
try:
from .eventio_async import EventIO
except:
from .eventio import EventIO