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 override for attr-defined errors (something like a promise to mypy that the attr is defined going forward) #18109

Open
zwimer opened this issue Nov 6, 2024 · 2 comments
Labels

Comments

@zwimer
Copy link

zwimer commented Nov 6, 2024

Feature

There are cases where mypy spits out an attr-defined error when accessing bar.foo; these can be ignore per-use with a # type: ignore[attr-defined], however, there doesn't currently seem to be a way to tell mypy that bar does indeed define foo.

In this case, it'd be nice if there was a way to tell mypy that bar does define foo, so that bar.foo can be used without spamming # type: ignore[attr-defined] comments everywhere.

How this is done I don't have an opinion about, but for example, it could be something like: # type: bar.foo: int.

Pitch

There are multiple useful cases of this:

  1. Although this is not the primary motivation, it would cover the case brought up here: Support hints for dynamically set attributes #15085

  2. This could allow type hinting for attributes added after a class definition. One example would be for users who enjoy using a TRACE log level with the logging module. That is, if a user adds a TRACE logging level to the logging module such as shown here: https://stackoverflow.com/questions/2183233/how-to-add-a-custom-loglevel-to-pythons-logging-facility
    In this instance, this would allow use of logging.trace and logging.TRACE without requiring #type: ignore[attr-defined] used at every occurrence.
    In general, this handles the case where you have something like:

class A:
    pass
A.B = "foo"    # Some comment to promise mypy that A has an attr named B defined
  1. Additionally, this can allow users to better handle when 3rd party libraries do not play nice with mypy. For example:
from cryptography.hazmat.primitives.serialization import load_ssh_private_key

This gives an attr-defined warning, although this import statement works just fine.

@zwimer zwimer added the feature label Nov 6, 2024
@TeamSpen210
Copy link
Contributor

Mypy does understand hasattr() - inside an if hasattr() block, it'll treat that attribute as Any, and narrow unions accordingly. That doesn't allow checking the type of the attribute itself, but you could just immediately assign it to an annotated variable to specify the type. Though this doesn't handle all the cases you mentioned.

For 1, probably what you'd need to do is cast the logger to a protocol object with the methods. For 2, you could add B: ClassVar[str] to A's definition, to make the attribute defined even if it's not there. For classes you don't own, you could use type stubs potentially to override the hints in the original module.

@zwimer
Copy link
Author

zwimer commented Nov 6, 2024

For hasattr; that would work, but it would be substituting 'forcing the user to spam # type: ignore[attr-defined] everywhere with if hasattr( code blocks at every usage. Plus it would, in my opinion, be bad form not to have an else: raise TypeError("foo should have .bar") leading to more spam.
That is to say, this would work, but it wouldn't really solve the problem.

For protocol (this is not my primary motivation for this feature, just a 'freebie'): I believe having to cast every time you access an element is equivalent, spam-wise, to having to add a # type: ignore label to everything. It is better in that the type isn't being ignored, but it also means the user has to be more aware of the existing object (as in they will have to keep the protocol up to date with the code it wraps). To me, this feels unnecessarily complicated and more likely to cause issues either with desync or not. I believe it is probably a better solution than # type: ignore, but given how spammy it would be I am not a fan of it.

For stubs: Is it possible to use stubs to append to an existing class? Or would you need to redefine the entire class' stubs? In which case, I don't know if that is feasible for multiple reasons, but the simplest being what if two different items modify the class but don't have knowledge of each other? If it is possible just to 'append', that might be similar enough to the feature that I am requesting. Specifically, the 'appended' stub would just be a promise to mypy that the attribute is defined- which is fundamentally the feature I am seeking. Preferably in a way that can be incorporated such that mypy autodetects the stubs.

I think of the listed above features, if there is an 'append' version of stubs, that'd potentially be acceptable solution depending on how that works. Otherwise, I don't believe any of the solutions above fully address the issue.

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

No branches or pull requests

2 participants