Created
April 4, 2024 10:14
-
-
Save jonathanslenders/552b0c0972d99585b67b1ddfe5416ef7 to your computer and use it in GitHub Desktop.
Mypy plugin that prevents abstract classes from having an `__init__` method.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from __future__ import annotations | |
| import typing | |
| from mypy.nodes import FuncDef | |
| from mypy.plugin import ClassDefContext, Plugin | |
| __all__ = ["NoInitForABCPlugin"] | |
| class NoInitForABCPlugin(Plugin): | |
| """ | |
| Mypy-plugin that enforces abstract classes (inheriting from `abc.ABC`) to | |
| not define an `__init__` method. | |
| We want abstract classes to be a pure abstract interface and not define any | |
| fields. Ideally, they should also not define any non-abstract methods, but | |
| this is less of a problem. | |
| Some literature for the justification: | |
| - "Nothing is something", Sandy Metz - https://www.youtube.com/watch?v=OMPfEXIlTVE&t=1s | |
| - "Subclassing in Python Redux", Hynek Schlawack - https://hynek.me/articles/python-subclassing-redux/ | |
| """ | |
| def get_base_class_hook( | |
| self, fullname: str | |
| ) -> typing.Callable[[ClassDefContext], None]: | |
| return self.no_init_check | |
| def no_init_check(self, ctx: ClassDefContext) -> None: | |
| for base in ctx.cls.info.bases: | |
| if base.type.fullname == "abc.ABC": | |
| for node in ctx.cls.defs.body: | |
| if isinstance(node, FuncDef) and node.name == "__init__": | |
| ctx.api.fail( | |
| "Abstract classes (inheriting from abc.ABC) " | |
| "should not define an __init__ method", | |
| ctx.cls, | |
| ) | |
| break | |
| def plugin(version: str) -> typing.Type[NoInitForABCPlugin]: | |
| return NoInitForABCPlugin |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment