If you care about code quality you are probably already familiar with linting-tools like flake8 and pylint. These tools automatically check your python code for common code-smells and anti-patterns.
Another excellent tool in this category is import-linter
. Import-linter can check that your dependency tree adheres to your chosen architecture. For example, in a Django project, it would be bad practice if your model
would import a view
or a form
. With import-linter, you can specify this as a layer-contract:
[importlinter:contract:1]
name=Lower layers (e.g. models) are not allowed to import higher layers (e.g. forms)
type=layers
containers=
your_main_app
layers=
views
forms
models
This contract specifies that
- you have an app-module
your_main_app
- inside that app-modules there are three layers, from the lowest to the highest:
models
,forms
,views
- lower layers are not allowed to depend on (i.e. import from) higher layers
Your project folder would look something like this:
project
├── .importlinter
└── my_main_app
├── __init__.py
├── forms.py
├── models.py # we'll add a "wrong" import here in the next step
└── views.py
When we run lint-imports
we will see that our contract is intact:
$ lint-imports
=============
Import Linter
=============
---------
Contracts
---------
Analyzed 4 files, 0 dependencies.
---------------------------------
Lower layers (e.g. models) are not allowed to import higher layers (e.g. forms) KEPT
Contracts: 1 kept, 0 broken.
Let’s break the contract
Let’s say you accidentally violate that self-imposed contract by having from .views import MyView
in your models.py
. Once you run lint-imports
, you will get the following error message:
$ lint-imports
=============
Import Linter
=============
---------
Contracts
---------
Analyzed 4 files, 1 dependencies.
---------------------------------
Lower layers (e.g. models) are not allowed to import higher layers (e.g. forms) BROKEN
Contracts: 0 kept, 1 broken.
----------------
Broken contracts
----------------
Lower layers (e.g. models) are not allowed to import higher layers (e.g. forms)
----------------------------------------------------
my_main_app.models is not allowed to import my_main_app.views:
- my_main_app.models -> my_main_app.views (l.1)
Excellent. lint-imports
caught the problem and pointed out the violating module and the line of code.
In addition to the “layer” contract type there are also “forbidden”, “independent”, and even a “custom” contract type. Check it out!
I can highly recommend adding import-linter
to your automatic python code quality checks in your CI pipeline and your git pre-commit hooks.
For completeness, here is the full .importlinter
config file used in this example:
[importlinter]
root_packages =
my_main_app
[importlinter:contract:1]
name=Lower layers (e.g. models) are not allowed to import higher layers (e.g. forms)
type=layers
layers=
views
forms
models
containers=
my_main_app
If you liked this post, please, do share it:
Thanks, for reading (and sharing)! 🥳