Import Linter is a pretty good command-line tool to check the rules that govern the architecture of a Python application. You can check more details about lint-imports on its readthedocs page.
First, you need to create a .importlinter file in the root of your project to define your contract(s). Now, from your project root, run lint-imports. That is enough to automatize the architecture review of your Python application.
One of the things that I missed when I started to use Import Linter is to be able to integrate it into my unit tests. Digging on the Import Linter source code, I found an easy way to integrate it into unit tests.
You only need to perform the following steps:
- import lint_imports method.
- Execute the method in a unit test and store the return value in a variable.
- Check if the result is equal to 0; in any other case, there are one or more architecture contracts broken.
from importlinter.cli import lint_imports def test_architecture_contracts(): result = lint_imports() assert 0 == result, "One of the architecture contract was broken"
If code is honoring the architecture contracts, then we should see the following output from the pytest command.
$ pytest -svv tests/test_architecture.py ========================================================= test session starts ========================================================= platform linux -- Python 3.8.2, pytest-3.8.2, py-1.8.1, pluggy-0.13.1 -- /home/jon/.virtualenvs/kakeibox-database-memory/bin/python cachedir: .pytest_cache rootdir: /home/jon/projects/kakeibox_project/kakeibox/kakeibox-database-dictionary, inifile: setup.cfg collected 1 item tests/test_architecture.py::test_architecture_contracts ============= Import Linter ============= --------- Contracts --------- Analyzed 7 files, 4 dependencies. --------------------------------- Layers contract KEPT Database classes independence contract KEPT Contracts: 2 kept, 0 broken. PASSED ====================================================== 1 passed in 0.20 seconds =======================================================
If there are some broken contracts, then you should see the following output.
pytest -svv tests/test_architecture.py ========================================================= test session starts ========================================================= platform linux -- Python 3.8.2, pytest-3.8.2, py-1.8.1, pluggy-0.13.1 -- /home/jon/.virtualenvs/kakeibox-database-memory/bin/python cachedir: .pytest_cache rootdir: /home/jon/projects/kakeibox_project/kakeibox/kakeibox-database-dictionary, inifile: setup.cfg collected 1 item tests/test_architecture.py::test_architecture_contracts ============= Import Linter ============= --------- Contracts --------- Analyzed 7 files, 5 dependencies. --------------------------------- Layers contract KEPT Database classes independence contract BROKEN Contracts: 1 kept, 1 broken. ---------------- Broken contracts ---------------- Database classes independence contract -------------------------------------- kakeibox_database_dictionary.database.account is not allowed to import kakeibox_database_dictionary.database.transaction: - kakeibox_database_dictionary.database.account -> kakeibox_database_dictionary.database.transaction (l.3) FAILED ============================================================== FAILURES =============================================================== _____________________________________________________ test_architecture_contracts _____________________________________________________ def test_architecture_contracts(): result = lint_imports() > assert 0 == result, "One of the architecture contract was broken" E AssertionError: One of the architecture contract was broken E assert 0 == 1 tests/test_architecture.py:6: AssertionError ====================================================== 1 failed in 0.25 seconds =======================================================
As you can see, integrate Import Linter into your unit tests can be quickly done. So on every unit test execution, you can check if your code is honoring the architecture contracts that are defined for your project, avoiding converting your application code in a big ball of mud with all the problems that come with it. Finally, keeping clean the architecture of your project for sure will save you a lot of headaches going forward.
Final notes
In case you are curious about the code that I used in the example, you can see it by yourself on https://gitlab.com/kakeibox/plugins/database/kakeibox-database-dictionary.
Relevant files:
- .importlinter: File that includes the architecture contracts for this project.
- test_architecture.py: File where you can find the unit test code.
Image credits
Python Developer with License
