One of the problems we get ourselves into is abstracting service io code into functions and calling them from our business logic code. This forces us to mock all the io calls, instead we should be doing the opposite i.e abstracting business logic into discrete functions of pure data in and out and then calling those functions from our io code. This way we can test the business logic independently of the io code. Testing the business logic now becomes quite simple and one can leverage property testing to further boost our confidence in such tests.
Many times the ramaining io code is just calling other functions and doesn't need to be tested.
Several other ideas that relate to this are hexagonal architecture, Domain Driver Design, clean architecture and functional core with an imperative shell.
Some resources to further explore these ideas:
Clean Architecture in Python - https://youtu.be/DJtef410XaM
Why integrated tests are a scam - https://youtu.be/VDfX44fZoMc
Boundaries - https://youtu.be/yTkzNHF6rMs