-
Notifications
You must be signed in to change notification settings - Fork 70
Add a protected contract type #266
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
Add a protected contract type #266
Conversation
bf35329 to
39125b4
Compare
39125b4 to
46312ca
Compare
|
Looking at #231, it could make sense to make external packages protected as well to solve this specific part of the issue. Maybe not the first version ? |
60b1861 to
2aec196
Compare
It's possible this might just work with external packages as-is. Worth adding a test case to see! |
seddonym
left a comment
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.
This is looking great so far.
I think it is worth improving how we report failed contracts, other than that hopefully most of my other comments are small tweaks.
Happy to discuss!
| illegal_imports = check.metadata["illegal_imports"] | ||
|
|
||
| output.print_error( | ||
| "Following imports do not respect the protected policy:", |
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.
I think we should improve the broken contract message. Taking inspiration from the layers contract, it might look something like this:
mypackage.green is not allowed to import mypackage.blue directly:
- mypackage.green.one -> mypackage.blue.two (l.6)
- mypackage.green.three -> mypackage.blue.four (l.12, 34)
- mypackage.green.five -> mypackage.blue.six (l. 4)
mypackage.yellow is not allowed to import mypackage.blue directly:
- mypackage.yellow.one -> mypackage.blue.two (l.16)
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.
This organisation makes sense with as_package=True only ?
Because I believe overlaps should be valid, I think it will be difficult to group reports by lines that triggered it 🤔, but it would be useful still.
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.
This organisation makes sense with as_package=True only ?
It's true that it has less value when as_packages is False, but maybe for consistency it's still worth presenting like that. The principle is that in the summary line ("x is not allowed to import y") we refer to the items listed in the contract, and then below we list the specific imports.
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.
I tried something in this commit
It would display error like this:
Following imports do not respect the protected policy:
mypackage.foo.protected rule is imported by unallowed modules:
- mypackage.bar.other_package.one -> mypackage.foo.protected.models (l.7)
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 a look. For me, the wording a bit too verbose - also I think it's worth making the reporting style more consistent with other contracts, including whitespace. Could we output the error in this form?
mypackage.green is not allowed to import mypackage.blue directly:
- mypackage.green.one -> mypackage.blue.two (l.6)
- mypackage.green.three -> mypackage.blue.four (l.12, 34)
- mypackage.green.five -> mypackage.blue.six (l. 4)
mypackage.yellow is not allowed to import mypackage.blue directly:
- mypackage.yellow.one -> mypackage.blue.two (l.16)
(Also let's drop the "Following imports do not respect the protected policy:" line, that should be obvious from the context.)
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.
As agreed, we will go with this version of the broken contract rendering:
[importlinter:contract:models-can-only-be-imported-by-colors]
name = Models can only be imported by colors direct descendant
type = protected
protected_modules =
mypackage.**.models
mypackage.**.data
mypackage.brown
allowed_importers =
mypackage.colors.*
as_packages = True
Models can only be imported by colors direct descendant
-------------------------------------------------------
Illegal imports of protected package mypackage.blue.models
(via mypackage.**.models expression):
- mypackage.green.one -> mypackage.blue.models (l.6)
- mypackage.green.three -> mypackage.blue.models (l.12, 34)
- mypackage.green.five -> mypackage.blue.models.alpha (l. 4)
Illegal imports of protected package mypackage.orange.models
(via mypackage.**.models expression):
- mypackage.yellow.one -> mypackage.orange.models (l.16)
Illegal imports of protected package mypackage.orange.data
(via mypackage.**.data expression):
- mypackage.yellow.one -> mypackage.orange.data (l.16)
Illegal imports of protected package mypackage.brown:
- mypackage.yellow.one -> mypackage.brown (l.16)
- mypackage.yellow.one -> mypackage.brown.alpha (l.16)
aef3d96 to
efd1d13
Compare
seddonym
left a comment
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 the changes!
It's nearly there but I do think we should improve the broken contract message before merging.
| ) | ||
|
|
||
| contract_check = contract.check(graph=graph, verbose=False) | ||
| assert not contract_check.kept |
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.
I think it would be worth asserting on the value of check.metadata["illegal_imports"] here, otherwise we don't have test coverage for that.
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.
I agree but I think we should review the way to write these tests then 🤔
Since we only add one import to the graph we're almost sure it will be the one, phrased differently we could make these tests pass with some logic that puts all the imports in "illegal_import" if the contract is not kept.
Maybe if I add some valid imports to the build graph + a test to check that the base imports let keep the contract ? 🤔
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.
Either way sounds fine - I think the main thing is that the unit test for rendering assumes that data is going to be stored in the metadata in a certain way, so we should also have a unit test that checks that the metadata is populated in that way. Does that make sense?
dd0a6b3 to
5529e37
Compare
@seddonym |
Yes that's correct (if you're interested, the logic is in Grimp here). I think it we probably need to external packages too, or at least document what isn't supported about them. I'm surprised they don't work though. Maybe a good way forward is to include an xfailing test in this PR, so I can see what you feel isn't working. |
24d05e8 to
100161c
Compare
f01cf5e to
16d2257
Compare
16d2257 to
99dee67
Compare
99dee67 to
b05aa7c
Compare
| ) | ||
|
|
||
| protected_modules = {top_level_protected_module} | ||
| if self.as_packages: |
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.
This should be if self.as_packages and not graph.is_module_squashed(top_level_protected_module):.
That's because external packages will be squashed, so they only appear as a single node in the graph.
That will sort the tests out!
| True, | ||
| "Allowed module can import external protected module", | ||
| ), | ||
| ( |
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.
This test case should be removed, as django.core would never appear in the graph as an external package. (Just django.)
seddonym
left a comment
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.
This is looking great! I've already tried it on a real life code base and it's going to be really helpful.
I see what the problem is with the external packages, it's a quick fix. There's a few other tweaks I'd like to make, so in the interests of not too much more back and forth, I'm going to merge into a branch and I'll make the tweaks there.
Thanks for your contribution, this is a really exciting one.
Add a new contract type to protect modules or packages from un-allowed imports