

A Debugging Tale - PyBites
source link: https://pybit.es/articles/debugging-tale/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

A debugging tale
By Bob Belderbos on 6 March 2022
I ran into an interesting issue debugging the other day …
I used isort with pre-commit to automatically sort imports before committing my code.
This is a huge time saver and I am very thankful for both tools, as well as black and flake8.
They save a ton of time and they instantly boost the quality of your code fixing easy-to-avoid mistakes shaving off debugging time.
I did a walk-through video the other day on how to use pre-commit here.
One thing that puzzled me though was isort’s behavior on the following test module:
# 01/test_app.py
import pytest
from fastapi.testclient import TestClient
from app import app
When running isort through pre-commit it sorted it like:
import pytest
from app import app # wtf?
from fastapi.testclient import TestClient
Which is not correct because app.py is my own module, not a third party one!
So I did a bit of debugging and I thought it would be interesting to share my approach here.
Reproducible case
Before doing anything else I checked for the easiest “reproducible case” to get more info from the program repeatedly. This is an essential debugging technique I picked up over the years.
In this case it meant running isort by itself (outside of pre-commit) and adding the -c and -v flags to it.
The -c flag does not edit the file in place (hence easy to repeatedly reproduce the issue). The -v flag adds useful information to isort’s command line output:
Reproducible case: running isort with -c and -v
So here we see the error in action: isort marked app.py unrightfully as THIRDPARTY.
Apart from that key insight I learned about isort’s messaging which brings me to the next step …
Debugging the source code
I checked out the isort repo and used ag
(a.k.a. the Silver Searcher, one of my favorites) to look for from-type place_module
:
ag is super useful to efficiently search a code base
(Update: the day after writing this article I learned that git grep -n <term>
achieves the same thing as ag
without the extra dependency – thanks Jeff!)
That led me to isort’s parse.py
where I added a breakpoint()
to drop into the pdb
debugger:
Setting a breakpoint based on isort’s messaging output
It’s important to note that the isort I had been using so far was “globally” installed with pipx:
$ which isort
/Users/bbelderbos/.local/bin/isort
So to use the locally checked out isort version (with the breakpoint in it), I used this command (which worked because there is a __main__.py package entry point to the package):
$ python -m isort ~/code/fastapi-sqlmodel/01 -c -v
…
I soon found out that finder
was finder = partial(place.module, config=config)
so I went one level deeper to the place.py
module and ended up inspecting this particular code:
Getting closer to the probable cause of the issue
I learned that all these conditionals were False
for app.py so isort hit the last condition for my “app” module:
(config.default_section, "Default option in Config or universal default.")
That gave me a hint that this might have been a config issue all along.
This brought me to isort’s config options where I learned that default_section
is THIRDPARTY
by default.
At least that explained the erroneous result. Digging a bit deeper I learned that you can actually force isort to recognize a module as “first party” with the -p flag:
Bingo, now it worked
But I felt this was merely solving the symptom, not the cause!
Going back to the module_with_reason
checks, I suspected that one of conditions these being False
could hint at a setup issue on my end (I really trust isort after all!)
Looking at the _local()
helper gave me the final hint:
Aha, the import system!
Ah! Is this an import path issue perhaps?!
And realizing () that I was actually running isort against 01/test_app.py, the next check was to
cd
into the 01 directory and try it again:
BINGO!
As I need these subdirectories because I am working on a PyBites Learning Path, I looked at an easy config fix for this …
I now had a way more targeted search string for Google thanks to the debugging effort:
isort import one level directory down
What stood out quickly glazing over the first comment was --known-local-folder
Second Google search:
pre-commit isort allow relative imports
This brought me back to the beforementioned config page.
It turns out you can set this in your isort config:
.isort.cfg
[settings]
known_local_folder=my_module1,my_module2
So I adapted that to my needs:
.isort.cfg
[settings]
known_local_folder=app
And then it all worked:
As most Bites will use app.py
this will probably do as a fix, but if a future module will be called api.py
, I can easily add it to this config file.
As this was not the only file it had failed on, as a last step I could retroactively fix it for all files using pre-commit’s --all-files
option: pre-commit run --all-files
> nice!
Lessons learned / takeaways from this story:
- Find an easy-and-fast-to-reproducible use case of the issue, here: run isort outside of pre-commit + use -c to not modify files in-place.
- Make your tools as verbose as possible, here this was easy:
isort -v
- With the messaging go to the source and see what it is doing,
breakpoint()
/pdb
for the win! - With the things you picked up debugging, you can search Google in a way more targeted manner.
- Bonus: I got to know isort a little bit better. Any time you spend on reading code usually has a great ROI!
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK