Open chrwang opened 9 months ago
I think I have the same Issue.
I've compared sys.path
between venvs with invoke==1.7.1
and invoke==2.2.0
. When invoke
is run from project directory (I will use examples above) /foo
, in the former version sys.path
gives ['/foo/pyinvoke', ...]
but for latter version it returns ['/foo/pyinvoke/tasks', ...]
which is the reason why I can't import from module on the same level as /foo/barutils
.
Moreover all commands in invoke -l
changed from barutils.command-x
to just command-x
.
Currently I have no other solution for this broken version than "hacking" sys.path
by replacing manually the first path which still leaves all commands changed to version without barutils.
.
Hack is to add this to foo_task.py
:
from pathlib import Path
import sys
sys.path[0] = str(Path(__file__).resolve().parent.parent)
Background
In
loader.py
,invoke
uses thisload
function to load thetasks
collection. This function attempts to detect whethertasks
is a module or a package:https://github.com/pyinvoke/invoke/blob/07b836f2663bb073a7bcef3d6c454e1dc6b867ae/invoke/loader.py#L49-L96
It first sets
enclosing_dir
to be the parent directory of whatever file the import resolved to (this istasks.py
for modules, and__init__.py
within the package directory for packages). Then, it checks if the discoveredModuleSpec
has a non-empty entry for.parent
, which would indicate that the importedtasks
is a package. If it is a package, it setsmodule_parent
to be one level aboveenclosing_dir
. If it is not a package,module_parent
is set to beenclosing_dir
.Bug
This is all correct, however the path that gets inserted into the path is always
enclosing_dir
, notmodule_parent
. If we have:and then resolve the
ModuleSpec
fortasks
, we correctly insert/foo/pyinvoke
into the path. However, if we have:we end up inserting
/foo/pyinvoke/tasks
into the path, which is wrong, the path inserted should still be/foo/pyinvoke
.Note: #944 is similar but predates the refactor to
load
that happened in 2.1.3. In https://github.com/pyinvoke/invoke/commit/aa8b815d7a7a43c8ac814c6deb9c4555b8ac6b9e logic was added to fix the search path for config files, but that did not extend to search path for packages.Note 2: This is all fine and dandy for most use cases where everything lives in
tasks
ortasks.py
. This only broke because we have the following structure:From within
foo_task.py
we havefrom barutils.bar import bartender
, which won't work when the inserted path is/foo/pyinvoke/tasks/
sincebarutils
isn't intasks
.Solution?
I'd like to propose that in:
https://github.com/pyinvoke/invoke/blob/07b836f2663bb073a7bcef3d6c454e1dc6b867ae/invoke/loader.py#L85
we replace
enclosing_dir
withmodule_parent
.@bitprophet if this fix seems reasonable I can put in a PR and add some tests for this.