Spack extensions can import code from modules in root or cmd folder#11209
Conversation
| # Create a searchable package for both the root folder of the extension | ||
| # and the subfolder containing the commands | ||
| ensure_package_creation(extension) | ||
| ensure_package_creation(extension + '.cmd') |
There was a problem hiding this comment.
The two lines above create two python packages named spack.extensions.<extension-name> and spack.extensions.<extension-name>.cmd. This means that:
- The
__init__.pyfile in those two extension folders is always executed when Spack imports commands - A user can refer to python modules either in the extension root or in any subfolder of the
<ext-root>/cmddirectory
There was a problem hiding this comment.
What happens if the __init__.py file doesn't exist in either case? Is it created, or treated as if it existed but was empty, or is this an error?
I'm also unsure as to what this does to solve this issue we had with SpackDev in the original implementation of PR #8612, namely that package globals weren't persisted across calls to functions in the package (in Python 2.7). Would it be possible to extend your test to verify behavior in this case? Specifically import a command, call one command to set a global and another to retrieve it, verifying that the value retrieved is as set?
Note that one thing the revised PR #11145 does that is not addressed here is to store the location of commands as they are found to avoid having to try and fail to import up to n-1 times for n configured extension paths.
|
|
||
| # Test a relative import | ||
| from ..implementation import hello_folks | ||
|
|
There was a problem hiding this comment.
The test here makes sure that both absolute and relative imports work.
|
@scheibelp @chissg If you are fine with the changes above I'll add docs for the extensions with this PR |
In the review I recognized the lack of unit tests in #8612, and started this PR to add them. Looking at the code linked in #11145 (comment) it seemed to me that what was missing from #8612 - please @chissg correct me if I'm wrong - was the possibility to import code outside of the spack-testcommand
└── testcommand
├── cmd
│ └── hello.py
├── implementation.py
└── __init__.pywhere the command in At that point I started reading through the mess that is the import mechanism in Python and figured out a less invasive solution to this based on package path rules, implemented it to check it worked and committed it here. Besides the fact that there are no API changes and no additional global variables added, I think the biggest difference this PR brings wrt #11145 is that by using package path rules instead of global spack/lib/spack/spack/extensions.py Lines 60 to 75 in 831f829 we effectively insulate extensions from one other, while having all the paths added in |
|
@scheibelp To add on the comment above, #11145 improves error handling for missing or mistyped commands - that part is not present in this PR, I just dealt with imports. |
| # Create a searchable package for both the root folder of the extension | ||
| # and the subfolder containing the commands | ||
| ensure_package_creation(extension) | ||
| ensure_package_creation(extension + '.cmd') |
There was a problem hiding this comment.
What happens if the __init__.py file doesn't exist in either case? Is it created, or treated as if it existed but was empty, or is this an error?
I'm also unsure as to what this does to solve this issue we had with SpackDev in the original implementation of PR #8612, namely that package globals weren't persisted across calls to functions in the package (in Python 2.7). Would it be possible to extend your test to verify behavior in this case? Specifically import a command, call one command to set a global and another to retrieve it, verifying that the value retrieved is as set?
Note that one thing the revised PR #11145 does that is not addressed here is to store the location of commands as they are found to avoid having to try and fail to import up to n-1 times for n configured extension paths.
It's treated as if empty, the code that deals with it is here: spack/lib/spack/spack/extensions.py Lines 58 to 63 in 9b1f1ca |
Sure. Can you be more specific on what you mean by "call one command to set a global and another to retrieve it"? Going back to the example above, do you mean something like:
? |
|
In the specific case I have in mind, dev.py, globals |
* New function spack.cmd.find_commands(), allowing for the location of subcommands. * New function spack.cmd.get_module_from() to allow subcommand module loading from alternative namespaces. * Improve error handling on missing, misspelled or broken command, including as part of `spack help`. If --debug is active, produce a stack trace. * Extension command paths are cached, including which commands are to be found at which path. * Corrected help text for --spec option (drive-by).
|
In addition to addressing #11209 (comment), could you also rename the |
b0fb212 to
b0dc9be
Compare
No prob renaming that, but I'd like later to improve this feature to allow for:
so I'm wondering whether we should look for a more general name than |
|
I'd prefer it be named I think that name would also be suitable later (even if the functionality you mention in #11209 (comment) is added).
Now that there are two types of extensions (extension packages, and command extensions), it would be best to be explicit about both. In the future I think the |
b0dc9be to
cdfcf7a
Compare
|
@scheibelp Done! |
| module_name = '{0}.{1}'.format(__name__, python_name) | ||
|
|
||
| try: | ||
| # TODO: Upon removal of support for Python 2.6 substitute the call |
There was a problem hiding this comment.
@alalazo sorry - another question: does this apply to all calls to load_module_from_file, or just this one? If it's just this one, then why is that?
There was a problem hiding this comment.
It's just this one. The reason has to do with how the import mechanism works (I had to read carefully through the docs to understand that). This snippet:
spack/lib/spack/spack/extensions.py
Lines 72 to 78 in cdfcf7a
is used to add a search __path__ to both the "root" extension module and its command submodule, e.g. spack.extensions.testcommand and spack.extensions.testcommand.cmd, which are imported here:
spack/lib/spack/spack/extensions.py
Lines 80 to 83 in cdfcf7a
After we do this, we won't need the absolute path of each command file anymore, as that will be computed from the __path__ of the corresponding subpackage. For instance, if I import spack.extensions.testcommand.cmd.hello the import mechanism will compute the corresponding file path from the first __path__ attribute found going up with subpackages i.e. spack.extensions.testcommand.cmd.
Now, in python 2.6 to import a module based on a computed string we need either to use directly the __import__ function or the imp module - and both approaches are not recommended. From python 2.7 on there's importlib.import_module which seems to be the idiomatic way and the comment above is a reminder that we could just use that instead of loading the module and adding it to sys.modules.
There was a problem hiding this comment.
@scheibelp Let me know if something should be clarified in the comment above or if it's fine like that. Thanks!
|
Thanks! |
* New function spack.cmd.find_commands(), allowing for the location of subcommands. * New function spack.cmd.get_module_from() to allow subcommand module loading from alternative namespaces. * Improve error handling on missing, misspelled or broken command, including as part of `spack help`. If --debug is active, produce a stack trace. * Extension command paths are cached, including which commands are to be found at which path. * Corrected help text for --spec option (drive-by).
* New function spack.cmd.find_commands(), allowing for the location of subcommands. * New function spack.cmd.get_module_from() to allow subcommand module loading from alternative namespaces. * Improve error handling on missing, misspelled or broken command, including as part of `spack help`. If --debug is active, produce a stack trace. * Extension command paths are cached, including which commands are to be found at which path. * Corrected help text for --spec option (drive-by).
* New function spack.cmd.find_commands(), allowing for the location of subcommands. * New function spack.cmd.get_module_from() to allow subcommand module loading from alternative namespaces. * Improve error handling on missing, misspelled or broken command, including as part of `spack help`. If --debug is active, produce a stack trace. * Extension command paths are cached, including which commands are to be found at which path. * Corrected help text for --spec option (drive-by).
* New function spack.cmd.find_commands(), allowing for the location of subcommands. * New function spack.cmd.get_module_from() to allow subcommand module loading from alternative namespaces. * Improve error handling on missing, misspelled or broken command, including as part of `spack help`. If --debug is active, produce a stack trace. * Extension command paths are cached, including which commands are to be found at which path. * Corrected help text for --spec option (drive-by).
* New function spack.cmd.find_commands(), allowing for the location of subcommands. * New function spack.cmd.get_module_from() to allow subcommand module loading from alternative namespaces. * Improve error handling on missing, misspelled or broken command, including as part of `spack help`. If --debug is active, produce a stack trace. * Extension command paths are cached, including which commands are to be found at which path. * Corrected help text for --spec option (drive-by).
* New function spack.cmd.find_commands(), allowing for the location of subcommands. * New function spack.cmd.get_module_from() to allow subcommand module loading from alternative namespaces. * Improve error handling on missing, misspelled or broken command, including as part of `spack help`. If --debug is active, produce a stack trace. * Extension command paths are cached, including which commands are to be found at which path. * Corrected help text for --spec option (drive-by).
* New function spack.cmd.find_commands(), allowing for the location of subcommands. * New function spack.cmd.get_module_from() to allow subcommand module loading from alternative namespaces. * Improve error handling on missing, misspelled or broken command, including as part of `spack help`. If --debug is active, produce a stack trace. * Extension command paths are cached, including which commands are to be found at which path. * Corrected help text for --spec option (drive-by).
* New function spack.cmd.find_commands(), allowing for the location of subcommands. * New function spack.cmd.get_module_from() to allow subcommand module loading from alternative namespaces. * Improve error handling on missing, misspelled or broken command, including as part of `spack help`. If --debug is active, produce a stack trace. * Extension command paths are cached, including which commands are to be found at which path.
* New function spack.cmd.find_commands(), allowing for the location of subcommands. * New function spack.cmd.get_module_from() to allow subcommand module loading from alternative namespaces. * Improve error handling on missing, misspelled or broken command, including as part of `spack help`. If --debug is active, produce a stack trace. * Extension command paths are cached, including which commands are to be found at which path.
* New function spack.cmd.find_commands(), allowing for the location of subcommands. * New function spack.cmd.get_module_from() to allow subcommand module loading from alternative namespaces. * Improve error handling on missing, misspelled or broken command, including as part of `spack help`. If --debug is active, produce a stack trace. * Extension command paths are cached, including which commands are to be found at which path.
* New function spack.cmd.find_commands(), allowing for the location of subcommands. * New function spack.cmd.get_module_from() to allow subcommand module loading from alternative namespaces. * Improve error handling on missing, misspelled or broken command, including as part of `spack help`. If --debug is active, produce a stack trace. * Extension command paths are cached, including which commands are to be found at which path.
* New function spack.cmd.find_commands(), allowing for the location of subcommands. * New function spack.cmd.get_module_from() to allow subcommand module loading from alternative namespaces. * Improve error handling on missing, misspelled or broken command, including as part of `spack help`. If --debug is active, produce a stack trace. * Extension command paths are cached, including which commands are to be found at which path.
* New function spack.cmd.find_commands(), allowing for the location of subcommands. * New function spack.cmd.get_module_from() to allow subcommand module loading from alternative namespaces. * Improve error handling on missing, misspelled or broken command, including as part of `spack help`. If --debug is active, produce a stack trace. * Extension command paths are cached, including which commands are to be found at which path.
* New function spack.cmd.find_commands(), allowing for the location of subcommands. * New function spack.cmd.get_module_from() to allow subcommand module loading from alternative namespaces. * Improve error handling on missing, misspelled or broken command, including as part of `spack help`. If --debug is active, produce a stack trace. * Extension command paths are cached, including which commands are to be found at which path.
* New function spack.cmd.find_commands(), allowing for the location of subcommands. * New function spack.cmd.get_module_from() to allow subcommand module loading from alternative namespaces. * Improve error handling on missing, misspelled or broken command, including as part of `spack help`. If --debug is active, produce a stack trace. * Extension command paths are cached, including which commands are to be found at which path.
This PR is a follow up of #8612 and an alternative implementation of the improved import capabilities in #11145.
The problem with #8612 was basically that for complex commands one couldn't split source code into python modules that were residing:
cmddirectoryThis PR solves that issue by ensuring that, for each extension found, the python packages associated with the root folder and the
cmdfolder are insys.moduleswith an appropriate__path__set.List of modifications: