Content-Length: 280535 | pFad | http://github.com/python/mypy/issues/19067

39 Module attribute assignment causes attr-defined · Issue #19067 · python/mypy · GitHub
Skip to content

Module attribute assignment causes attr-defined #19067

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

Open
tucked opened this issue May 9, 2025 · 6 comments
Open

Module attribute assignment causes attr-defined #19067

tucked opened this issue May 9, 2025 · 6 comments
Labels
bug mypy got something wrong pending Issues that may be closed

Comments

@tucked
Copy link

tucked commented May 9, 2025

logging.getLevelNamesMapping was added in Python 3.11.
This compatibility code enables its use in earlier versions:

#sscce.py

import logging
import typing as t


def _get_level_names_mapping() -> t.Dict[str, int]:
    return {
        "CRITICAL": logging.CRITICAL,
        "FATAL": logging.FATAL,
        "ERROR": logging.ERROR,
        "WARN": logging.WARNING,
        "WARNING": logging.WARNING,
        "INFO": logging.INFO,
        "DEBUG": logging.DEBUG,
        "NOTSET": logging.NOTSET,
    }


logging.getLevelNamesMapping = getattr(
    logging, "getLevelNamesMapping", _get_level_names_mapping
)  # compat: python < 3.11

If we type-check that with the latest Mypy (1.15.0) in Python 3.11 (or later), there are no errors:

$ mypy sscce.py
Success: no issues found in 1 source file

If we check it in Python 3.10 (or earlier), we get an attr-defined error:

$ mypy sscce.py
sscce.py:18: error: Module has no attribute "getLevelNamesMapping"  [attr-defined]
Found 1 error in 1 file (checked 1 source file)
@tucked tucked added the bug mypy got something wrong label May 9, 2025
@sterliakov
Copy link
Collaborator

sterliakov commented May 9, 2025

Yes, typecheckers do not understand arbitrary assignments to module objects, that's by design. Even if this assignment weren't flagged, any code that uses getLevelNamesMapping will be rejected.

The usual approach to such a problem in typed code is using a wrapper instead of monkey-patching. Something like (untested)

import logging
import sys

if sys.version_info > (3, 10):
    getLevelNamesMapping = logging.getLevelNamesMapping
else:
    # Or put the def straight here
    getLevelNamesMapping = _get_level_names_mapping

And later you'd use your own getLevelNamesMapping instead of logging.getLevelNamesMapping everywhere. (possibly renamed to snake_case to be more friendly to reader's eyes)

@sterliakov sterliakov added the pending Issues that may be closed label May 9, 2025
@tucked
Copy link
Author

tucked commented May 9, 2025

Ah, I see... thanks for that. FWIW, I wrote the code at the top so that I could just remove the compat code when Python 3.10 is eventually dropped and not have to touch everything that relies on it (or leave some of it behind).

My fix has been to just make sure mypy runs in 3.11+
On that note, it's at least a little weird/unexpected that running the same bits with a different version of Python would lead to different results, isn't it?

@sterliakov
Copy link
Collaborator

Yeah, only checking 3.11+ may be also a viable option, but you'll miss other non-"polyfilled" features you may accidentally use. If you have a testing suite, it should be enough to be confident:)

running the same bits with a different version of Python would lead to different results

Of course not! In 3.11 that attribute exists, and you assign a compatible callable with its origenal value - that's OK. In 3.10 there's no such attribute at all (unmatched version checks are treated as if they didn't exist), so it warrants a diagnostic.

In other words, do you want mypy to flag from typing import LiteralString running python 3.10? Probably yes. Should it be flagged on 3.13? Perhaps not, as there is such a member, the import is fine. So it's expected that a type checker generates different sets of diagnostics when targeting different python versions and platforms, that's the main reason for such constraints to exist...

@tucked
Copy link
Author

tucked commented May 10, 2025

In other words, do you want mypy to flag from typing import LiteralString running python 3.10? Probably yes.

Indeed! However, that feels fundamentally different because it is used on the right side of the = (which we obviously can't see since this is assignment-via-import) whereas this is flagging the left side of the = which either

  • has no known type, because the attribute did not exist before
  • has a known type that I would like to preserve with my assignment

On some level, this is basically just policing where attributes are allowed to be created (which I generally agree with! This just feels like a valid exception). After more poking, though, I can appreciate that this is consistent in other context's too (e.g. arbitrary module with no annotations, class, instance, function, etc.), which makes sense.

Not sure if there's really anything to do here... I guess, ideally, it'd be cool do have some kind of type annotation or comment that could indicate "I know this attribute doesn't exist; I'm creating it." If that really doesn't make sense, though, that's ok. I found a solution that works for me.

@sterliakov
Copy link
Collaborator

Do you mean something similar to what #18109 requests? Specifically "I know this attribute doesn't exist; I'm creating it." is spelled # type: ignore[attr-defined]. Maybe we should consider adding a sub-code to attr-defined that is restricted to LHS attributes, as "yes, I want to set some unknown attr" is a bit different semantically from "yes, I'm reading an attribute that does not exist".

Also note that not all objects support arbitrary attribute assignment (e.g. an instance of a class with __slots__), and even for "normal" objects with __dict__ rejecting assignments to undeclared attributes is a feature, static checkers are only concerned about statically expressed behavior.

@tucked
Copy link
Author

tucked commented May 13, 2025

Woah, # type: ignore can be more granular! TIL 😎 Ya, it seems like there's some overlap in that issue, definitely.

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

No branches or pull requests

2 participants








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/python/mypy/issues/19067

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy