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
664 stars 45 forks source link

independence between children of a package #207

Closed florianabel closed 9 months ago

florianabel commented 9 months ago

First of all, thanks for the great work with this package, it's really useful. Secondly, I am hoping, that my question is not out of scope, though I think it's a valid use case.

Use case

I am having a package with several sub-packages of integrations, i.e.

my_package.integrations | |---->integation_a |---->integration_b ...

I am packaging them together for shipping reasons but would like to ensure, that they do not depend on each other and was trying to use an independence contract for it, but I could not get it to run like this.

Implementation intent

[[tool.importlinter.contracts]] name = "My independence contract" type = "independence" modules = [ "my_package.integrations" ]

Also tried: [[tool.importlinter.contracts]] name = "My independence contract" type = "independence" modules = [ "my_package.integrations", "my_package.integrations" ]

Working but sub-optimal solution

I could define all integrations specifically, but that would require future developers to continue adding them to the list when new integrations are being added and kind of defeats the purpose for me. [[tool.importlinter.contracts]] name = "My independence contract" type = "independence" modules = [ "my_package.integrations.integration_a", "my_package.integrations.integration_b" ]

seddonym commented 9 months ago

Hi, thanks for suggestion!

I think this is a duplicate of https://github.com/seddonym/import-linter/issues/56, so I'm going to close this one - feel free to comment further on that PR.

Another option which you could use right now is to use a layers contract instead, and define the packages as sibling layers:

[importlinter:contract:my-layers-contract]
name = Contract with sibling layers
type = layers
containers = my_package.integrations
layers=
    integration_a | integration_b
exhaustive  = true

The advantage of this is that layers contracts support the exhaustive flag, which means that if a developer adds an integration without listing it here, the contract will fail. You could add a comment to the contract to ensure that the integration gets added as a sibling layer - or you could even add an additional test that verifies that your contract is the shape you're expecting by reading the contract using the Python API.

Hope helps!

florianabel commented 9 months ago

Thanks a lot @seddonym! I think this will work for now, very helpful.

Will have a closer look and then maybe get back on it if further need be.