Flake8 plugin enforcing composition over inheritance
Project description
flake8-inheritance
A Flake8 plugin that nudges you toward composition over inheritance. It detects when classes inherit from concrete internal classes — cases where composition would reduce coupling — and flags concrete methods in abstract base classes that should remain pure interfaces.
Status: Under development. Not yet published to PyPI.
Compatibility
- Python: 3.11, 3.12, 3.13
- Flake8: 6.x, 7.x
- Operating systems: Linux, macOS, Windows
Installation
pip install flake8-inheritance
Quickstart
-
Install the plugin (it registers itself with Flake8 automatically):
pip install flake8-inheritance
-
Enable the plugin rules (it is off by default):
flake8 --enable-extensions=INH src/
Or configure this once in
.flake8/setup.cfg:[flake8] enable-extensions = INH
-
Configure
--project-packagesso the plugin knows which imports are part of your project. Add it to.flake8orsetup.cfg:# .flake8 or setup.cfg [flake8] enable-extensions = INH project-packages = myproject
Without this option, same-file inheritance and relative imports (e.g.
from .models import Base) are still flagged. However, absolute imports from your own packages are treated as external and silently allowed. -
Run Flake8:
flake8 src/
Verify the plugin is loaded
flake8 --version
The output should include a line like:
flake8-inheritance: X.Y.Z
Error codes
| Code | Description |
|---|---|
| INH001 | Inheritance from an internal concrete class (use composition) |
| INH002 | Concrete method in an abstract base class (ABCs should be pure) |
INH001 — inheritance from internal class
Triggered when a class inherits from a concrete (non-abstract) class
that lives in the same file or in one of the configured
--project-packages.
class Engine:
def start(self) -> None: ...
# INH001: Inheritance from internal class 'Engine' is not allowed
class TurboEngine(Engine):
def start(self) -> None: ...
Fix: replace inheritance with composition:
class TurboEngine:
def __init__(self, engine: Engine) -> None:
self._engine = engine
def start(self) -> None:
self._engine.start()
INH002 — concrete method in an ABC
Triggered when a class that derives from abc.ABC (or uses
abc.ABCMeta) defines a non-abstract method.
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def speak(self) -> str: ...
# INH002: ABC 'Animal' contains concrete method 'legs'
def legs(self) -> int:
return 4
Fix: make the method abstract, or move the default implementation into a concrete class.
Configuration
This section documents every plugin option and the most common Flake8-native
settings used to adopt flake8-inheritance in real projects.
Defaults at a glance
| Option | Default | Meaning |
|---|---|---|
--enable-extensions |
unset | Plugin checks are disabled until INH is enabled |
--project-packages |
empty (unset) | Absolute imports are external unless package is configured |
--inh002-allowed-dunders |
unset (None) |
All dunder methods are allowed in ABCs |
--project-packages
Comma-separated list of top-level package names that belong to your project. The plugin uses this to distinguish your code from third-party libraries.
[flake8]
enable-extensions = INH
project-packages = myproject,myproject_utils
Default: empty (unset)
When left unset (or set to an explicit empty value), the plugin still flags:
- inheritance from classes defined in the same file
- inheritance via relative imports (for example,
from .base import Base)
But it treats absolute imports as external unless their top-level package is listed here.
--inh002-allowed-dunders
Comma-separated list of dunder method names that are permitted as
concrete methods in ABCs (e.g., __init__, __repr__).
[flake8]
enable-extensions = INH
inh002-allowed-dunders = __init__,__repr__
Default: unset (None), which allows all dunder methods.
This option has three useful states:
- Unset (no config value): all dunder methods are allowed.
- Empty string (
inh002-allowed-dunders =): no dunder methods are allowed. - Non-empty list: only listed dunder methods are allowed.
Full configuration reference by file format
You can configure these options in whichever Flake8 config style your project uses.
.flake8
[flake8]
enable-extensions = INH
project-packages = myproject,myproject_utils
inh002-allowed-dunders = __init__,__repr__
# Flake8-native filtering controls
select = INH,E,F,W
extend-ignore = E203
per-file-ignores =
tests/*:INH001
setup.cfg
[flake8]
enable-extensions = INH
project-packages = myproject,myproject_utils
inh002-allowed-dunders = __init__,__repr__
# Flake8-native filtering controls
select = INH,B,C,E,F,W
extend-ignore = E203,W503
per-file-ignores =
tests/*:INH001
src/myproject/legacy/*.py:INH001,INH002
pyproject.toml (with flake8-pyproject)
Flake8 does not read pyproject.toml natively. Use
flake8-pyproject to enable
this format.
[tool.flake8]
enable-extensions = ["INH"]
project-packages = ["myproject", "myproject_utils"]
inh002-allowed-dunders = ["__init__", "__repr__"]
# Flake8-native filtering controls
select = ["INH", "E", "F", "W"]
extend-ignore = ["E203", "W503"]
per-file-ignores = [
"tests/*:INH001",
"src/myproject/legacy/*.py:INH001,INH002",
]
Flake8-native options commonly used with this plugin
These are provided by Flake8 itself (not this plugin), but they are important for adoption:
--select: run only selected code families (for example,--select=INHto focus exclusively on inheritance rules).--extend-ignore: suppress specific codes while keeping defaults.--per-file-ignores: carve out exceptions for known legacy paths.
Gradual Adoption (report-only workflow)
For existing codebases, adopt incrementally:
- Start in report-only mode by running Flake8 with
--select=INHin CI, but do not fail the build yet. - Track and review findings; classify true positives vs intentional patterns.
- Add temporary
per-file-ignoresfor legacy modules to keep signal high. - Fix violations in new/changed code first.
- Remove ignores over time and then enforce INH rules as required checks.
Example report-only CI command:
flake8 --select=INH src tests || true
Example enforced command once ready:
flake8 --select=INH src tests
Known limitations
- Dynamic base classes are skipped: if a base class is constructed dynamically (for example, through metaprogramming or runtime factory calls), the plugin cannot resolve it statically and does not emit INH001 for that base. Workaround: favor explicit, statically imported base classes where you want enforcement.
- Import aliases keep only top-level package classification: aliases such as
from pkg.module import Base as RenamedBaseare tracked underRenamedBase, but classification still uses only the top-level package (pkg), not the full defining module path (pkg.module). - Star imports are skipped for unresolved names: with
from pkg.module import *, names introduced indirectly cannot be reliably mapped to import paths, so inheritance checks for those names are skipped. Workaround: replace star imports with explicit imports. - Re-exports through
__init__.pyare classified by top-level package name: imports likefrom pkg import Xare classified aspkgwithout tracing to a defining submodule, so re-exports are not distinguished from direct exports. Workaround: import directly from the defining module when that distinction matters. - Analysis is single-file only: decisions are made from one file's AST and
import statements without cross-file type inference.
Workaround: configure
project-packagesand use explicit imports to improve internal/external classification accuracy within this single-file model.
License
BSD-3-Clause
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file flake8_inheritance-0.1.0.tar.gz.
File metadata
- Download URL: flake8_inheritance-0.1.0.tar.gz
- Upload date:
- Size: 104.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
924bf7551cf43d8ca7b06be57427379ee637a29b9ac771bfc9df95fc025972cf
|
|
| MD5 |
f4fc7c3db8b0024f3db6eae466aa7163
|
|
| BLAKE2b-256 |
5806c0595b72c777f574d0f559543daa61f90ca3000b733bb585cf4b9b5d163f
|
Provenance
The following attestation bundles were made for flake8_inheritance-0.1.0.tar.gz:
Publisher:
publish.yml on steven-cutting/flake8-composition-over-inheritance
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
flake8_inheritance-0.1.0.tar.gz -
Subject digest:
924bf7551cf43d8ca7b06be57427379ee637a29b9ac771bfc9df95fc025972cf - Sigstore transparency entry: 941913865
- Sigstore integration time:
-
Permalink:
steven-cutting/flake8-composition-over-inheritance@acb542919c42cdcfd7b6a41a6bd5967571789930 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/steven-cutting
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@acb542919c42cdcfd7b6a41a6bd5967571789930 -
Trigger Event:
release
-
Statement type:
File details
Details for the file flake8_inheritance-0.1.0-py3-none-any.whl.
File metadata
- Download URL: flake8_inheritance-0.1.0-py3-none-any.whl
- Upload date:
- Size: 12.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1ae6424b0b37f4c5037609c82d8bd9afd271aedb15f77fa7a727c47ef5c42239
|
|
| MD5 |
4e0981ddde6421c8ad015bfd75e34567
|
|
| BLAKE2b-256 |
094948f6e04a6cee5ddf2ed987d2b656fa7da9d6159c9c5fd3e4663e57dee87e
|
Provenance
The following attestation bundles were made for flake8_inheritance-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on steven-cutting/flake8-composition-over-inheritance
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
flake8_inheritance-0.1.0-py3-none-any.whl -
Subject digest:
1ae6424b0b37f4c5037609c82d8bd9afd271aedb15f77fa7a727c47ef5c42239 - Sigstore transparency entry: 941913869
- Sigstore integration time:
-
Permalink:
steven-cutting/flake8-composition-over-inheritance@acb542919c42cdcfd7b6a41a6bd5967571789930 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/steven-cutting
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@acb542919c42cdcfd7b6a41a6bd5967571789930 -
Trigger Event:
release
-
Statement type: