Skip to content

Instantly share code, notes, and snippets.

@jonathanslenders
Created April 4, 2024 10:14
Show Gist options
  • Select an option

  • Save jonathanslenders/552b0c0972d99585b67b1ddfe5416ef7 to your computer and use it in GitHub Desktop.

Select an option

Save jonathanslenders/552b0c0972d99585b67b1ddfe5416ef7 to your computer and use it in GitHub Desktop.
Mypy plugin that prevents abstract classes from having an `__init__` method.
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