-
Notifications
You must be signed in to change notification settings - Fork 51
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
Disallow imports not specified through __all__ #240
Comments
Interesting one this, thanks for raising. It's actually something we're also grappling with at work at the moment - broadly speaking I would describe this as wanting to achieve encapsulation - that is, a separation between the public interface and the private internals of a given subpackage. And it looks like you're wanting to make Django models a private concern - again, that's something we're working on. In terms of your specific feature request, I suspect adding support for this in Import Linter would be a substantial amount of work. Currently it relies on building a directed graph of all the imports between modules. Supporting your use case would require more granularity about the objects exposed in each module. I wonder if this might be relatively straightforward to lint for without building an import graph - perhaps a regular expression might be enough, as the concern is quite localized. Or you could write a flake8 plugin, or request an addition to Ruff. Another option would be to import the model with a private alias, like this?
At the very least, that will give people second thoughts before importing Incidentally, the convention we currently follow is to prefix private modules with an underscore. So you could move to a structure like this:
Of course, that would break Django's autodiscovery of models - because really, models have to be public as far as Django is concerned. But there's probably a way around it, such as running an autodiscovery of I anticipate that Import Linter will support enforcing the rules based on underscores in the not too distant future. Another tactic could be to use the Repository Pattern, and structure your application so that the implementation of the repositories is in a layer above the code that interacts with the repository abstraction. That's something Import Linter currently supports (via the Let me know how you get on! |
Thanks for the very detailed answer @seddonym, greatly appreciated!
That unfortunately the conclusion we reached as well. We were hoping there was a way to have the graph built with objects as nodes instead of modules but that's not possible.
We also considered this pattern and encouraged its usage but alas without enforcement we always ended up with unintended violations. What I mean is that it's not so much the importing of I guess there might be a combination of linters out there that might make our life easier so I'll keep looking.
That's great news!
We actually implemented a similar concept that we called interfacing contract paired with CODEOWNERS. Interfacing contracts are defined with three configurations
So something like [auth]
covering = auth
interfaces =
auth.interface
auth.exceptions
auth.types
ignore_imports =
foo.bar -> auth.private # known violation In order to ensure that only code owners of can allow or deny changes to contracts covering verticals they own we also added a way to have the top level So we basically we have a top level |
That concept of an interfacing contract is cool! I think something like that might work well as a built in contract for Import Linter (or could be implemented as a separate library, since Import Linter supports pluggable contract types).
Tell me more! Currently you have to put all the contracts in the same file to use the same built graph - it would be nice to be able to split the contracts up into different files for reasons of maintainability. |
👋 there, thanks for that awesome project!
We've been using
import-linter
to define interfaces between packages in our application but one thing that keeps coming is preventing re-exports from violating our contracts.For example, say that we allow module
foo
to only import frombar.interface
(and nothing else inbar.*
) when we definebar.interface
we might imports a few things frombar.*
to define the interface.We'd like for a way to prevent imports from
bar.*
inbar.interface
from being re-exported and we were hoping we could usebar.interface.__all__
for that through some flags at least instead of doing function level imports inbar.interface
.It might be easier to reason about this with code. Today if we want to prevent re-exports of
bar.*
inbar.interface
we have to doAs otherwise doing
re-exports
Bar
asbar.interface.Bar
. We'd like to be able to doand have import linter prevent
bar.interface.Bar
imports.Thank you for your consideration!
The text was updated successfully, but these errors were encountered: