Closed gmmeyer closed 1 year ago
We're seeing the same problem
inv --list
Traceback (most recent call last):
File "/Users/rberger/.pyenv/versions/3.11.2/bin/inv", line 8, in <module>
sys.exit(program.run())
^^^^^^^^^^^^^
File "/Users/rberger/.pyenv/versions/3.11.2/lib/python3.11/site-packages/invoke/program.py", line 387, in run
self.parse_collection()
File "/Users/rberger/.pyenv/versions/3.11.2/lib/python3.11/site-packages/invoke/program.py", line 479, in parse_collection
self.load_collection()
File "/Users/rberger/.pyenv/versions/3.11.2/lib/python3.11/site-packages/invoke/program.py", line 716, in load_collection
module, parent = loader.load(coll_name)
^^^^^^^^^^^^^^^^^^^^^^
File "/Users/rberger/.pyenv/versions/3.11.2/lib/python3.11/site-packages/invoke/loader.py", line 80, in load
spec.loader.exec_module(module)
File "<frozen importlib._bootstrap_external>", line 940, in exec_module
File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
File "/code/tasks/__init__.py", line 2, in <module>
from . import install, utils
ModuleNotFoundError: No module named 'tasks'
Thanks for the reports! Almost certainly something to do with #919, except all our tests and CI pass (eg: https://app.circleci.com/pipelines/github/pyinvoke/invoke/288/workflows/9aa4cffc-8199-4158-ae94-cf29e1dd761d). I think the crux is:
I have a directory /tasks/*.py
I can confirm that this breaks in my one (personal) project using a tasks/ directory, but works everywhere else, so it's clearly specific to the package-not-module variant. We do have tests for that though, see eg this test and the package it should be loading.
@kuwv you got time to hunt this down? If not I will try to take a look early next week.
Anyone afflicted should have no problem temporarily pinning to invoke<2.1
, 2.1 itself doesn't have anything super juicy in it.
Unfortunately, I'm currently traveling at the moment. Soonest I'll be able get to it might be Wednesday.
Dug some (but also got sniped by adjacent issue that throws irritating test errors, now fixed). Already kind of cross as I blew half my freakin Saturday getting turned in circles with all this, but the upshot is:
__init__.py
since the actual proximate symptom is tasks/__init__.py
trying to do eg from . import submodule
.
tasks/__init__.py
holding its own self contained tasks, and not doing any from-self imports, does not reproduce the error - though it's possible I saw this during the 3.6 vs 3.7 confusion so it needs a double check.My offhand guess is either:
imp
did supports this pattern, in a way that the new use of importlib
does not, eg maybe imp
was somehow performing path manipulation under the hood.Loader.load
around the sys.path.insert
call, but I just triplechecked that and it looks safe; def needs some debug printouts to be sure though)I will try taking a closer look early next week.
Moar dig:
imp
either) but at a glance, the derived module spec seems accurate wrt its submodule_search_locations
(it's a singleton list containing the tasks/
directory, as we would expect) and its origin
(tasks/__init__.py
)from . import module
and calling next
, and manually doing the import call via the ipdb REPL, work fine with no error.sys.path
continues to look like it did earlier (which was from viewpoint of the loader).So this is pretty weird. Will continue reading importlib's docs and/or seeing if this is somehow a stdlib bug (would be pretty damn weird if so, to exist for so long & in such a critical spot, people do from .
imports all the time these days).
sys.modules
also not helping here, it does not contain the local tasks
package before the import in any scenario.
tasks
and tasks.module
(now renamed to tasks.wtf
btw 😇) appear afterwards in sys.modules, but again, neither appear before then.exec_module
to the "legacy" load_module
works great! so I guess that's the fallback plan for now...yes, it's deprecated, but it's still importlib instead of imp
? 🙃
__path__
is identical to the 'new style' one's submodule_search_locations
.create_module
followed by exec_module
that "the import machinery does everything for you" but clearly there is some kind of gap here.
spec_from_file_location
to get a spec, then stuffs that into module_from_spec
, which is described as using create_module
...so we should be compliant here?https://docs.python.org/3/library/importlib.html#approximating-importlib-import-module implies we might be missing a step (or two) here, such as manually manipulating sys.modules
. This does seem to work!
(As long as I don't fuck up and do that manipulation after the exec_module
call instead of before...wasn't reading the example closely enough, but in hindsight it makes perfect sense, execution of the module-level code in our tasks file is what needs to 'see' the module?)
I'm a bit salty that this "new improved" importlib API still requires the user to do this, but I guess I also shouldn't fault other folks for "here's our new much more flexible API and also oops you still have to do some manual labor in corner cases that wasn't required before, sorry"...
So it looks like @kuwv missed this one spot when adapting that importlib example snippet to Invoke's loader classes. I'm going to put out a bugfix on this basis, assuming all the other tests pass for me afterwards...
In trying to get the unit test for this breaking properly, I found:
__init__.py
(ie the only useful code of any sort is in package.module
), so that's awkward. was that ever supposed to work? I don't think so...package
task tree.package.module
. so that's almost surely what is making this test incidentally pass (by accidentally warming up the cache in sys.modules), but is its own mystery.
pytest -k
to select just the package test, it fails as we expect. God bless testing...Thank you!
I have a directory /tasks/*.py with a bunch of python files in it. With 2.1.0 I suddenly get the following error:
It works perfectly in 2.0.0. It seems like there was a regression of some kind