Skip to content

Commit c6f518b

Browse files
committed
Keep mypy from thinking Git has arbitrary class attributes
The existence of __getattr__ or __getattribute__ on a class causes static type checkers like mypy to stop inferring that reads of unrecognized instance attributes are static type errors. When the class is a metaclass, this causes static type checkers to stop inferring that reads of unrecognized class attributes, on classes that use (i.e., that have as their type) the metaclass, are static type errors. The Git class itself defines __getattr__ and __getattribute__, but Git objects' instance attributes really are dynamically synthesized (in __getattr__). However, class attributes of Git are not dynamic, even though _GitMeta defines __getattribute__. Therefore, something special is needed so mypy infers nothing about Git class attributes from the existence of _GitMeta.__getattribute__. This takes the same approach as taken to the analogous problem with __getattr__ at module level, defining __getattribute__ with a different name first and then assigning that to __getattribute__ under an `if not TYPE_CHECKING:`. (Allowing static type checkers to see the original definition allows them to find some kinds of type errors in the body of the method, which is why the method isn't just defined in the `if not TYPE_CHECKING`.) Although it may not currently be necessary to hide __setattr__ too, the same is done with it in _GitMeta as well. The reason is that the intent may otherwise be subtly amgiguous to human readers and maybe future tools: when __setattr__ exists, the effect of setting a class attribute that did not previously exist is in principle unclear, and might not make the attribute readble. (I am unsure if this is the right choice; either approach seems reasonable.)
1 parent 04eb09c commit c6f518b

File tree

1 file changed

+9
-2
lines changed

1 file changed

+9
-2
lines changed

Diff for: git/cmd.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -335,16 +335,23 @@ class _GitMeta(type):
335335
This helps issue :class:`DeprecationWarning` if :attr:`Git.USE_SHELL` is used.
336336
"""
337337

338-
def __getattribute__(cls, name: str) -> Any:
338+
def __getattribute(cls, name: str) -> Any:
339339
if name == "USE_SHELL":
340340
_warn_use_shell(False)
341341
return super().__getattribute__(name)
342342

343-
def __setattr__(cls, name: str, value: Any) -> Any:
343+
def __setattr(cls, name: str, value: Any) -> Any:
344344
if name == "USE_SHELL":
345345
_warn_use_shell(value)
346346
super().__setattr__(name, value)
347347

348+
if not TYPE_CHECKING:
349+
# To preserve static checking for undefined/misspelled attributes while letting
350+
# the methods' bodies be type-checked, these are defined as non-special methods,
351+
# then bound to special names out of view of static type checkers.
352+
__getattribute__ = __getattribute
353+
__setattr__ = __setattr
354+
348355

349356
class Git(metaclass=_GitMeta):
350357
"""The Git class manages communication with the Git binary.

0 commit comments

Comments
 (0)