-
Notifications
You must be signed in to change notification settings - Fork 70
AcyclicContract #250
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AcyclicContract #250
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for taking an look at this!
I've left a few comments. Overall, the direction of travel really would be to implement most of this in Rust, in the Grimp library - and then just have a thin wrapper around it in Import Linter. But there's a bit to do before we get there.
My current feeling is it might make more sense to release this as a separate package for the time being, but I could be talked around!
Interested to know your thoughts.
721ad3c to
eaa6987
Compare
eaa6987 to
45289b0
Compare
This comment was marked as resolved.
This comment was marked as resolved.
|
Thanks for sharing! I will take a look (though it might be a week or two before I get to it). That's cool about PyCon Greece (and of course you can talk about Import Linter). Do please share a recording of the talk once it's available, I'd love to watch. |
|
Thanks, no rush :) I will share the presentation if it gets recorded. |
434fbf1 to
e304ad1
Compare
| continue | ||
|
|
||
| if not graph.find_matching_modules(expression=package): | ||
| msg = f"Package '{package}' does not exist in the import graph." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to think about external packages as they will be nodes in the graph, but they won't have any descendants. E.g. if include_external_packages = true, django will be there but we won't have enough information to check for acyclic dependencies within it.
I think the correct check would be also to check the module isn't squashed: https://grimp.readthedocs.io/en/stable/usage.html#ImportGraph.is_module_squashed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I added a static method _is_internal_module.
|
Thanks for latest updates! Tests are failing though...I'll wait until they pass before trying this on Django again. Now you've rebased off master you can make use of improved tooling for working locally, e.g. |
Thanks, |
The main thing I think we need to focus on is defining how, precisely, this is going to behave. So don't worry too much about implementing everything (e.g. it would be fine to mock out the response from Grimp). The key is to understand Grimp's API with respect to cycle detection, and then how we present it in Import Linter to end users. |
|
I've been doing some more thinking about this feature - just noting down some findings. Weighted edgesI think it is useful to think of the 'squashed' version of the graph (i.e. the graph showing the dependencies between subpackages of a single module) as a weighted graph, where the edge weights are the number of imports. For example, the
This is much more helpful information - we can conclude, for example, that Minimal Weighted Feedback Arc SetWe can then find the Minimal Weighted Feedback Arc Set: this represents the minimum number of imports we'd need to This is provided in the igraph library: https://igraph.org/python/api/0.9.6/igraph._igraph.GraphBase.html#feedback_arc_set The good news is that although the problem is NP-hard, which means for larger graphs it can take an excessive amount of time, the fact that we are running it on a squashed graph seems to mean in practice it is a quick calculation. I have tried running this on a few cyclic graphs and it seems to pick out sensible results quickly. So, rather than reporting to the user which cycles exist (which could be a large number even in the case of smallish cyclic graphs like How to render failuresWe could report this something like this: Or, relative style (inconsistent with other contracts, but more concise): Or we could present it as a summary per-subpackage. Wrapping upSo, my current thinking is that we want to get to a place where there is an API in Grimp for the minimum weighted arc feedback set. I'll have a think about what that API should look like. Then Import Linter will drill down the levels to whatever depth is configured in the contract, and call that API and report on the cycles in one of these three ways. There is further help we could offer users who are trying to understand what to do about an acyclic graph, such as visualizations. I think this should be out of scope for Import Linter - instead we could implement it within Impulse, a currently tiny package which I also maintain. Perhaps Import Linter could even recommend the use of the tool in its error message. What are your thoughts? Does this feel like a good direction? |
I really like it, rendering the issue together with a solution. That sounds really helpful for users. Let me review it more carefully this weekend after I am back from vacations. |
|
I've made a start on documenting an API in Grimp here. |
|
Great! One thing to note is it doesn't support the cycles between parents and children we identified as a different type of cycle. Possibly that could be a different method in Grimp.
Whatever you prefer. Once we've finalized the Grimp API, we could adapt this PR or start a new one.
Yes sounds good!
Received! Thanks. |
|
This is what I meant by the depth argument. It would default to drilling down to all sub packages, but we could stop it at a certain depth. |
|
I am going to concentrate on implementing |
|
Also, I have been thinking about the other kind of cycle - the one that can happen between parents and children, and which I'd like to concentrate on the |
|
FYI, I think I have a working concept here now: python-grimp/grimp#253 Next steps are to add some more thorough tests, and possibly see if there is a way to avoid the igraph dependency - but I might be happy to live with that dependency temporarily, since it is a killer feature IMO! |
Thanks for sharing. I will take a closer look on the new grimp API soon. I think it makes more sense to abandon this PR and start from a scratch based on the new
EDIT: |
|
I'm pleased to say that I have now released the latest Grimp, which gives us nominate_cycle_breakers. I should have time to integrate this into the |
That's really cool the functionality is already in Grimp. Thanks for updating me with a status and possibility to contribute. However, I think you should know better how to implement the remaining part from the I will try to update my presentation on PyCon Wroclaw with things you mentioned. Hopefully, some new people will start to use |
Sounds good. I'm aiming to get this done this week, I'll keep you posted. I'm closing this PR now, but thanks for all your help pushing the feature forward and helping clarify the thinking around it, it's just as important as submitting code. |
|
Acyclic Siblings contracts are now available in the latest Import Linter. I'm very excited about this feature and have already started using it. Thanks @K4liber for making this happen! https://import-linter.readthedocs.io/en/stable/contract_types.html#acyclic-siblings |

Introducing a new contract that checks whether the dependency graph of modules (or packages) stick to an acyclic dependencies principle (ADP).
It indicates that modules (or packages) dependencies form a directed acyclic graph (DAG).