seddonym / import-linter

Import Linter allows you to define and enforce rules for the internal and external imports within your Python project.
https://import-linter.readthedocs.io/
BSD 2-Clause "Simplified" License
692 stars 49 forks source link

New contract type: modular #133

Open stacey-gammon opened 2 years ago

stacey-gammon commented 2 years ago

Hello! I noticed in your blog post about this package, you mention circular dependencies being a reason you created it. I couldn't find any examples of preventing circular dependencies using the built-in contracts, however.

Will I need to build my own, or do any of the three included contracts provide this functionality?

seddonym commented 2 years ago

Hi Stacey,

Great question.

The way I prevent circular dependencies is by writing layers contracts. But that involves making a decision about how the dependencies should flow, which is ultimately an act of design. If you've got a preexisting project you could try running my other tool Impulse on it to give you clues about where the dependencies are currently flowing.

I have been wondering about a more general contract that just detects circular dependencies, without any further input. But that would be based on my own definition of circular dependencies, which is essentially that, at each level of a package hierarchy, the descendants of each module don't have cycles. For example, if mypackage.foo.blue imports mypackage.bar.green, and mypackage.bar.yellow imports mypackage.foo.orange, I would view that as a circular dependency. But that's just my own opinion about how to structure Python projects.

Does that make sense?

mwgamble commented 1 year ago

For example, if mypackage.foo.blue imports mypackage.bar.green, and mypackage.bar.yellow imports mypackage.foo.orange, I would view that as a circular dependency. But that's just my own opinion about how to structure Python projects.

In my opinion ( :stuck_out_tongue: ) it's perfectly fine for software to have opinions. Developers won't be forced to use it, and it could inspire someone to create a different (potentially better) circular dependency detection algorithm.

seddonym commented 1 year ago

Thinking a bit more about it, such a contract could look like this:

[importlinter:contract:my-contract]
name = My contract
type = modular
containers=
    mypackage

This would check that there are no cycles between the child packages of mypackage. For example, if mypackage.blue.one imported mypackage.green.two, and mypackage.green.three imported mypackage.blue.four, that would be illegal.

We could also add a 'depth' argument to get it to do the same deeper in the tree. For example if this had a depth of 2, it would check that there are no cycles between the children of each child. For example, within mypackage.blue it wouldn't allow mypackage.blue.one.alpha to import mypackage.blue.two.beta if mypackage.blue.two.gamma imports mypackage.blue.one.delta.

What I like about this is that it requires less up-front design, people could just enable it on a new project and then the linter will automatically detect when the package stops being modular.