pyinvoke / invoke

Pythonic task management & command execution.
http://pyinvoke.org
BSD 2-Clause "Simplified" License
4.41k stars 369 forks source link

Import is resolving the wrong module #937

Open fraenkel opened 1 year ago

fraenkel commented 1 year ago

If you have a submodule under tasks that has the same name as a top-level module, the import will fail.

For example, I have a module named common which is located in my site-packages. My tasks directory looks like:

/project
  /tasks
    __init__.py
    common.py

The issue is pretty simple, https://github.com/pyinvoke/invoke/blob/main/invoke/loader.py#L77

import common now comes from /project/tasks instead of my site-packages.

fraenkel commented 1 year ago

If you have a module in the /tasks directory with the same name as a package in site-packages, they will resolve to the same name because the /tasks directory was added to sys.path.

Here is an example of the failure:

diff --git a/integration/_support/package/tasks/module.py b/integration/_support/package/tasks/module.py
index 05f37ee5..5bca5e80 100644
--- a/integration/_support/package/tasks/module.py
+++ b/integration/_support/package/tasks/module.py
@@ -1,4 +1,6 @@
 from invoke import task
+from . import pytest as pt
+from pytest import Testdir

 @task
diff --git a/integration/_support/package/tasks/pytest.py b/integration/_support/package/tasks/pytest.py

When you run inv -l

✗ inv -l
Traceback (most recent call last):
....
ImportError: cannot import name 'Testdir' from 'pytest' 

If we look at sys.modules, we see:

'tasks.pytest': <module 'tasks.pytest' from '/invoke/integration/_support/package/tasks/pytest.py'>, 
'pytest': <module 'pytest' from '/invoke/integration/_support/package/tasks/pytest.py'>}

which is wrong.

fraenkel commented 1 year ago

Comparing 2.0 with 2.1.1, the main difference is sys.path. 2.0: /invoke/integration/_support/package is added 2.1.1: /invoke/integration/_support/package/tasks is added