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 declaring classes in try/except #4691

Closed
proegssilb opened this issue Aug 5, 2023 · 5 comments
Closed

Support for declaring classes in try/except #4691

proegssilb opened this issue Aug 5, 2023 · 5 comments
Assignees

Comments

@proegssilb
Copy link

Due to CircuitPython intentionally not supporting the typing module, you instead have to get creative:

try:
    from typing import TypeVar, Generic

    T = TypeVar("T")

    class BaseClass(Generic[T]):
        pass
except ImportError:
    class BaseClass(object):
        pass

class GenericExampleClass(BaseClass):
    pass

However, when I do this, Pylance registers two errors:

Class declaration "BaseClass" is obscured by a declaration of the same namePylance[reportGeneralTypeIssues](https://github.com/microsoft/pyright/blob/main/docs/configuration.md#reportGeneralTypeIssues)
buttonanimationcontroller.py(13, 11): See class declaration
(class) BaseClass

(The line & column number here point to the except clause)

The second error:

Argument to class must be a base classPylance[reportGeneralTypeIssues](https://github.com/microsoft/pyright/blob/main/docs/configuration.md#reportGeneralTypeIssues)
(class) BaseClass

Note that BaseClass is indeed a base class.

You can get only the second error if you do the following:

try:
    from typing import TypeVar, Generic

    T = TypeVar("T")

    BaseClass = Generic[T]
except ImportError:
    BaseClass = object

class GenericExampleClass(BaseClass):
    pass

Regardless of whether or not this code is ideal, is there a way we could make this code work a little more nicely?

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

You're providing two different (incompatible) type declarations for the same symbol in the same scope. This breaks a fundamental rule of a static type checker.

If you don't want to use static type checking, you can disable these errors by setting python.analysis.typeCheckingMode to "off".

If you want to leave static type checking enabled, you'll need to provide a workaround in your code.

  1. Use typing_extensions instead of typing. This external library provides backward compatibility for typing features.
  2. Define your own constant and tell pyright (the type checker that underlies pylance) that you would like it to assume that this value is always True or False.
CIRCUIT_PYTHON = False # Replace with appropriate check

if CIRCUIT_PYTHON:

    class BaseClass(object):
        pass

else:
    from typing import TypeVar, Generic

    T = TypeVar("T")

    class BaseClass(Generic[T]):
        pass

In your pyproject.toml file:

[tool.pyright]
defineConstant = { CIRCUIT_PYTHON = false }
  1. Suppress individual errors using # type: ignore or # pyright: ignore comments at the end of the appropriate lines.

For more information, please refer to pyright's documentation;

@proegssilb
Copy link
Author

proegssilb commented Aug 14, 2023

It seems my example wasn't quite complete enough. While your suggestion was helpful, it's not quite the complete example for my situation.

My current MCVE is this:

CIRCUIT_PYTHON = False

try:
    from typing import TypeVar, Generic
    CIRCUIT_PYTHON = False

except ImportError:
    CIRCUIT_PYTHON = True

if CIRCUIT_PYTHON:
    T = object
    
    class BaseClass(object):
        pass
else:
    T = TypeVar('T')

    class BaseClass(Generic[T]):
        pass
    
    
class GenericExampleClass(BaseClass[T]):
    placeholder_var: T

This fails at runtime with the error: TypeError: 'type' object isn't subscriptable . Because CircuitPython does not understand typing beyond "ignore the type annotations", I can't have the [T] outside the conditional logic. Removing it results in the error: Type variable "T" has no meaning in this context on the type annotation for placeholder_var . I've looked at type aliases (don't seem relevant) and type stubs (seem cumbersome for this scenario, but if they're the best option here, than so be it), and no ideas regarding how to remove the generics subscript come to mind readily.

The class I've renamed to GenericExampleClass is a core class in this code base, and could be used with a variety of types. It'd be valuable to figure out a way to annotate this type correctly, but I'll admit, this is definitely a learning opportunity for me.

EDIT: Oh, and I also posted on Adafruit's support forums with no response yet.

@StellaHuang95
Copy link
Contributor

Hi @proegssilb, this construct isn't supported in CircuitPython:

class GenericExampleClass(BaseClass[T]):
    placeholder_var: T

so you'll have to declare the placeholder_var as an object like this:

class GenericExampleClass(BaseClass):
    placeholder_var: object

If you want type checking just when using Pylance, you can do something like this:

from typing import TYPE_CHECKING

CIRCUIT_PYTHON = False

try:
    from typing import TypeVar, Generic
    CIRCUIT_PYTHON = False

except ImportError:
    CIRCUIT_PYTHON = True

if not TYPE_CHECKING:
    T = object
    class BaseClass(object):
        pass

else:
    T = TypeVar('T')
    class BaseClass(Generic[T]):
        pass

class GenericExampleClass(BaseClass):
    placeholder_var: object

@StellaHuang95 StellaHuang95 added by design and removed needs repro Issue has not been reproduced yet labels Aug 29, 2023
@proegssilb
Copy link
Author

By inheriting from Generic[T], BaseClass itself becomes generic, and therefore requires a type-subscript when used in GenericExampleClass.

Further, the entire typing module doesn't exist in CircuitPython, so the attempt to import TYPE_CHECKING raises an ImportError.

But, interesting approach with making placeholder_var an object instead of a T. Not ideal, but clearly some level of compromise will have to be made in order to get as much typechecking as possible.

@proegssilb
Copy link
Author

In case anyone finds this in the future, what I actually do is manually maintain a .pyi file. This way I get precise typing, and CircuitPython doesn't know what's up. The class is stable, so hopefully the maintenance burden isn't that high.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants