Slightly tangential, but I've found importlinter neat for restricting what layers in your Python code can call each other (in this case by restricting imports, not calls): https://pypi.org/project/import-linter/
For example, you can ensure lower-level packages don't import higher-level ones:
[importlinter:contract:my-layers-contract]
name = Layered architecture
type = layers
layers =
mypackage.cli # can import anything
mypackage.services # can't import from mypackage.cli
mypackage.models # can't import from .cli or .services
For example, you can ensure lower-level packages don't import higher-level ones: