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
679 stars 49 forks source link

Forbid importing from parent module #124

Open POD666 opened 2 years ago

POD666 commented 2 years ago

I have tried to define following contract:

[[tool.importlinter.contracts]]
name = "No project_web imports from alembic migration files"
type = "forbidden"
source_modules = [
    "project_web.migrations.versions"
]
forbidden_modules = [
    "project_web",
]

But it fails with:

$ lint-imports --debug
Traceback (most recent call last):
  File "/Users/viktordanyliuk/.pyenv/versions/venv/bin/lint-imports", line 8, in <module>
    sys.exit(lint_imports_command())
  File "/Users/viktordanyliuk/.pyenv/versions/3.9.9/envs/venv/lib/python3.9/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/Users/viktordanyliuk/.pyenv/versions/3.9.9/envs/venv/lib/python3.9/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/Users/viktordanyliuk/.pyenv/versions/3.9.9/envs/venv/lib/python3.9/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/viktordanyliuk/.pyenv/versions/3.9.9/envs/venv/lib/python3.9/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/Users/viktordanyliuk/.pyenv/versions/3.9.9/envs/venv/lib/python3.9/site-packages/importlinter/cli.py", line 42, in lint_imports_command
    exit_code = lint_imports(
  File "/Users/viktordanyliuk/.pyenv/versions/3.9.9/envs/venv/lib/python3.9/site-packages/importlinter/cli.py", line 70, in lint_imports
    passed = use_cases.lint_imports(
  File "/Users/viktordanyliuk/.pyenv/versions/3.9.9/envs/venv/lib/python3.9/site-packages/importlinter/application/use_cases.py", line 43, in lint_imports
    raise e
  File "/Users/viktordanyliuk/.pyenv/versions/3.9.9/envs/venv/lib/python3.9/site-packages/importlinter/application/use_cases.py", line 40, in lint_imports
    report = create_report(user_options, show_timings)
  File "/Users/viktordanyliuk/.pyenv/versions/3.9.9/envs/venv/lib/python3.9/site-packages/importlinter/application/use_cases.py", line 71, in create_report
    return _build_report(
  File "/Users/viktordanyliuk/.pyenv/versions/3.9.9/envs/venv/lib/python3.9/site-packages/importlinter/application/use_cases.py", line 138, in _build_report
    check = contract.check(copy_of_graph)
  File "/Users/viktordanyliuk/.pyenv/versions/3.9.9/envs/venv/lib/python3.9/site-packages/importlinter/contracts/forbidden.py", line 72, in check
    chains = graph.find_shortest_chains(
  File "/Users/viktordanyliuk/.pyenv/versions/3.9.9/envs/venv/lib/python3.9/site-packages/grimp/adaptors/graph.py", line 264, in find_shortest_chains
    raise ValueError("Modules have shared descendants.")
ValueError: Modules have shared descendants.

I end up listing all modules from project_web in forbidden_modules except migrations to avoid it.

Would be nice to have a better way to do it as now I need to add every new module to the contract.

Does it sound possible?

seddonym commented 2 years ago

Thanks for raising this.

The reason it's failing is because, as per the docs, descendants of each module will also be checked.

I suppose there's an argument to say that forbidden contracts should allow source modules to import themselves and their descendants, regardless of what's listed in forbidden_modules. But it does complicate the forbidden contract, so I'm not sure.

Let me have a think about it. In the meantime, you could implement this behaviour relatively easily as a custom contract type. Let me know if anything in the documentation is unclear.