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

Forbidden Contract Error With Namespace Package #224

Closed bwildenborg closed 3 months ago

bwildenborg commented 4 months ago

Hello, I have a namespace package, and when I try to use a forbidden contract with import-linter 2.0 I get an error:

Verbose mode.
Building import graph (with caching disabled)...
Traceback (most recent call last):
  File "/home/brad/projects/teck/.venv/lib/python3.10/site-packages/grimp/application/usecases.py", line 130, in _scan_packages
    raise caching.CacheMiss
grimp.application.ports.caching.CacheMiss

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/brad/projects/teck/.venv/bin/lint-imports", line 8, in <module>
    sys.exit(lint_imports_command())
  File "/home/brad/projects/teck/.venv/lib/python3.10/site-packages/click/core.py", line 1157, in __call__
    return self.main(*args, **kwargs)
  File "/home/brad/projects/teck/.venv/lib/python3.10/site-packages/click/core.py", line 1078, in main
    rv = self.invoke(ctx)
  File "/home/brad/projects/teck/.venv/lib/python3.10/site-packages/click/core.py", line 1434, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/brad/projects/teck/.venv/lib/python3.10/site-packages/click/core.py", line 783, in invoke
    return __callback(*args, **kwargs)
  File "/home/brad/projects/teck/.venv/lib/python3.10/site-packages/importlinter/cli.py", line 52, in lint_imports_command
    exit_code = lint_imports(
  File "/home/brad/projects/teck/.venv/lib/python3.10/site-packages/importlinter/cli.py", line 99, in lint_imports
    passed = use_cases.lint_imports(
  File "/home/brad/projects/teck/.venv/lib/python3.10/site-packages/importlinter/application/use_cases.py", line 57, in lint_imports
    raise e
  File "/home/brad/projects/teck/.venv/lib/python3.10/site-packages/importlinter/application/use_cases.py", line 54, in lint_imports
    report = create_report(user_options, limit_to_contracts, cache_dir, show_timings, verbose)
  File "/home/brad/projects/teck/.venv/lib/python3.10/site-packages/importlinter/application/use_cases.py", line 112, in create_report
    graph = _build_graph(
  File "/home/brad/projects/teck/.venv/lib/python3.10/site-packages/importlinter/application/use_cases.py", line 162, in _build_graph
    return settings.GRAPH_BUILDER.build(
  File "/home/brad/projects/teck/.venv/lib/python3.10/site-packages/importlinter/adapters/building.py", line 21, in build
    return grimp.build_graph(
  File "/home/brad/projects/teck/.venv/lib/python3.10/site-packages/grimp/application/usecases.py", line 57, in build_graph
    imports_by_module = _scan_packages(
  File "/home/brad/projects/teck/.venv/lib/python3.10/site-packages/grimp/application/usecases.py", line 133, in _scan_packages
    direct_imports = import_scanner.scan_for_imports(
  File "/home/brad/projects/teck/.venv/lib/python3.10/site-packages/grimp/adaptors/importscanner.py", line 73, in scan_for_imports
    walker.visit(ast_tree)
  File "/home/brad/.pyenv/versions/3.10.4/lib/python3.10/ast.py", line 410, in visit
    return visitor(node)
  File "/home/brad/.pyenv/versions/3.10.4/lib/python3.10/ast.py", line 418, in generic_visit
    self.visit(item)
  File "/home/brad/.pyenv/versions/3.10.4/lib/python3.10/ast.py", line 410, in visit
    return visitor(node)
  File "/home/brad/projects/teck/.venv/lib/python3.10/site-packages/grimp/adaptors/importscanner.py", line 363, in visit_ImportFrom
    self._parse_direct_imports_from_node(node, self.from_import_parser)
  File "/home/brad/projects/teck/.venv/lib/python3.10/site-packages/grimp/adaptors/importscanner.py", line 382, in _parse_direct_imports_from_node
    for imported in parser.determine_imported_modules(node):
  File "/home/brad/projects/teck/.venv/lib/python3.10/site-packages/grimp/adaptors/importscanner.py", line 266, in determine_imported_modules
    external_module = self._distill_external_module(untrimmed_module)
  File "/home/brad/projects/teck/.venv/lib/python3.10/site-packages/grimp/adaptors/importscanner.py", line 195, in _distill_external_module
    while external_path_components[0] == internal_path_components[0]:
IndexError: list index out of range`

I cut out all of my code, so my namespace package is effectively just:

ns
|- one
|-- __init__.py

Where init.py is empty. My .importlinter file looks like:

[importlinter]
root_packages= 
    ns.one
include_external_packages = True
exclude_type_checking_imports = True

[importlinter:contract:no-pytest]
name = Do not import pytest
type = forbidden
source_modules =
    ns.one
forbidden_modules = 
    pytest

If I switch this to a standard package and update the config to refer to ns instead of ns.one, it works as expected.

bwildenborg commented 4 months ago

Actually, it appears to be related to include_external_packages = True with the namespace package. Once turned on, I get the error even if there are no external packages in the contracts.

seddonym commented 3 months ago

Thanks for your bug report!

I'm unable to reproduce the problem though. What directory are you running lint imports from?

bwildenborg commented 3 months ago

Looks like this was an issue with some otherwise benign import cycles. Poor way to surface the error, but it looks like more of an issue with how grimp surfaces the error rather than import-linter.