Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dependencies: Make installing plugin packages optional #333

Merged
merged 6 commits into from
Mar 4, 2024

Conversation

sphuber
Copy link
Collaborator

@sphuber sphuber commented Feb 26, 2024

Fixes #233
Fixes #290

Installing all plugin packages is quite costly and most users are not likely to want to use all codes. Therefore, an optional requirement is created for each code that implements the common workflow interface.

To make it easy to still install all plugin packages, the all_plugins optional requirement group adds the union of all the plugin optional requirements. The downside is that this requires duplicating the requirements and risks getting out of sync.

Built in [all] support was proposed in PEP 426 but this was rejected: https://peps.python.org/pep-0426

@sphuber sphuber force-pushed the fix/install-optional-requirements branch 2 times, most recently from 34b305e to 7ada291 Compare February 26, 2024 21:57
@sphuber
Copy link
Collaborator Author

sphuber commented Feb 26, 2024

@bosonie what do you think of this proposal? As mentioned in the commit, having to duplicate the requirements in pyproject.toml is not ideal, but as far as I can tell, there is no dynamic way of providing the all_plugins extras, and I think it is necessary to have it because forcing someone to type out all extras if they want support for all plugins is shit UX.

Was thinking about further improvements:

  • ✅ Done: Improve the docs to mention the extras (installation is anyway missing and will be adding this soon, see documentation should mention how to install the software #290 )
  • ✅ Done: Add custom pre-commit hook that somehow checks the dependency requirements for the extras are consistent, to prevent them getting out-of-sync
  • Find a way to catch ModuleNotFoundError that is likely to be thrown if a plugin is not installed and the user tries to use its implementation. Would be great if we could reliably catch this and point to the docs on how to install the prerequisites for a particular plugin
  • ✅ Done: Add tests to ensure that certain parts of the code are functioning even without any plugins installed

@sphuber sphuber force-pushed the fix/install-optional-requirements branch from 986111c to 907e3d7 Compare February 28, 2024 11:01
@sphuber
Copy link
Collaborator Author

sphuber commented Feb 28, 2024

@bosonie I have added a pre-commit hook check for the consistency of the all_plugins extras. In addition, I have also added tests to make sure the plugin-independent parts of that package can be imported.

@sphuber sphuber force-pushed the fix/install-optional-requirements branch 2 times, most recently from 34e8858 to a427012 Compare February 28, 2024 12:07
@bosonie
Copy link
Collaborator

bosonie commented Feb 28, 2024

@sphuber I had a look at all the commits from my phone. It looks all good to me. The strategy is the same I would implement.
I would have loved to have more time to test it, but I will not be able before Saturday.
Up to you if you want to wait (and maybe sort the last point regarding the NonImplementedError) or you prefer to merge and we will sort any possible problem later on.

@sphuber
Copy link
Collaborator Author

sphuber commented Feb 29, 2024

@sphuber I had a look at all the commits from my phone. It looks all good to me. The strategy is the same I would implement. I would have loved to have more time to test it, but I will not be able before Saturday. Up to you if you want to wait (and maybe sort the last point regarding the NonImplementedError) or you prefer to merge and we will sort any possible problem later on.

Thanks for having a look @bosonie . It can wait till next week. It would be great to have another pair of eyes on it and if you can test it a bit.

Regarding catching ModuleNotFoundErrors: the tricky question is where to put it. I though about subclassing the WorkflowFactory of aiida-core and catching the ImportError. Since we then have the entry point string, which should include the plugin name, we can automatically format the help message to say the should run pip install aiida-common-workflows[plugin_name]. This would require that all code in ACWF consistently uses the custom factory, but that is doable.

However, this does not catch everything. If the code manually imports a plugin module, e.g., from aiida_siesta import workflows, then this cannot be automatically caught. We would have to wrap this in a try/catch ourselves and print the message. This is certainly doable, but it will require some care on our part to make sure that every time a plugin import is added, it is done within this try/catch.

Thinking about it now, it probably still makes sense to do it, even though it won't be perfect and there is a risk for mistakes slipping through the cracks. But in the majority of cases it will give the user actual useful instructions for them to solve the problem themselves and to avoid them coming to the forums with the same questions. I will add another commit on top here later today I think.

@sphuber sphuber force-pushed the fix/install-optional-requirements branch from b90a276 to 9a2c9b1 Compare February 29, 2024 22:08
@sphuber
Copy link
Collaborator Author

sphuber commented Feb 29, 2024

Ok @bosonie @giovannipizzi , I now also added the last idea of using a custom workflow factory to print pip install instructions when a plugin is requested that is not installed.

This PR is now complete and ready for final review.

Copy link
Member

@giovannipizzi giovannipizzi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @sphuber ! I gave a look to the code and your comments - I think it implements all I think it should. I'm approving it, but since @bosonie said he would have given a careful check tomorrow (and I didn't check the code myself) I suggest we wait to merge until next week, in case he has specific comments.

Copy link
Collaborator

@bosonie bosonie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @sphuber. I've been reviewing the code and I have to report a missing change that is necessary to complete the final feature that raises a better explained MissingEntryPointError.
When running through the CLI, this is the function called to load the workchains:

def load_workflow_entry_point(workflow: str, plugin_name: str):
"""Load the entry point for the given plugin implementation of a certain common workflow.
:param workflow: the name of the common workflow.
:param plugin_name: name of the plugin implementation.
:return: the workchain class of the plugin implementation of the common workflow.
"""
prefix = f'{PACKAGE_PREFIX}.{workflow}.{plugin_name}'
return entry_point.load_entry_point('aiida.workflows', prefix)

This function must be modified to use the new aiida_common_workflow.plugins.factories.WorkflowFactory.

I'll continue a bit tomorrow to test few things but I believe the rest is all good.

@sphuber sphuber force-pushed the fix/install-optional-requirements branch 2 times, most recently from 9bc4901 to 303f3df Compare March 3, 2024 08:16
@bosonie
Copy link
Collaborator

bosonie commented Mar 3, 2024

@sphuber I had a look at the documentation and here you should change the import.

from aiida.plugins import WorkflowFactory

@sphuber sphuber force-pushed the fix/install-optional-requirements branch from 303f3df to ccf5e9b Compare March 3, 2024 21:42
sphuber added 6 commits March 3, 2024 22:47
Installing all plugin packages is quite costly and most users are not
likely to want to use all codes. Therefore, an optional requirement is
created for each code that implements the common workflow interface.

To make it easy to still install all plugin packages, the `all_plugins`
optional requirement group adds the union of all the plugin optional
requirements. The downside is that this requires duplicating the
requirements and risks getting out of sync.

Built in `[all]` support was proposed in PEP 426 but this was rejected:
https://peps.python.org/pep-0426
This provides the `tomllib` package with the standard library which is
necessary for an upcoming pre-commit hook.
The `dev/validate_optional_dependencies.py` is added. It validates that
the `all_plugins` extras specifies exactly the same dependency
requirements that all other extras combined declare as well, except for
the `docs`, `pre-commit`, and `tests` extras, which are only used for
development.

This is to ensure that the `all_plugins` extras provides the exact same
dependencies as all the plugin specific extras combined. The script is
called through a pre-commit hook.
A new job `tests-minimal-install` is added to the CI and CD workflows.
The job installs the package with minimal extra dependencies (just the
`tests` extras are installed so that `pytest` is available`. The job
then invokes `pytest` with the `-m minimal_install` option. The
`minimal_install` marker is added to run only those tests that should
run with a minimal install.

For now, the minimal install tests simply check that all modules are
importable, except for the workflow plugin implementations, and that all
CLI commands can be called with the `--help` option.
The `WorkflowFactory` from `aiida-core` is replaced with a custom
version in the `aiida_common_workflows.plugins.factories` module. This
function will call the factory from `aiida-core` but catch the
`MissingEntryPointError` exception. In this case, if the entry point
corresponds to a plugin implementation of one of the common workflows
the exception is reraised but with a useful message that provides the
user with the install command to install the necessary plugin package.

While this should catch all cases of users trying to load a workflow for
a plugin that is not installed through its entry point, it won't catch
import errors that are raised when a module is imported directly from
that plugin package. Therefore, these imports should not be placed at
the top of modules, but placed inside functions/methods of the
implementation as much as possible.
@sphuber sphuber force-pushed the fix/install-optional-requirements branch from ccf5e9b to 2bb9b03 Compare March 3, 2024 21:48
@sphuber
Copy link
Collaborator Author

sphuber commented Mar 3, 2024

@sphuber I had a look at the documentation and here you should change the import.

from aiida.plugins import WorkflowFactory

Done. I also fixed the docs in a separate PR, so now all checks are passing.

@bosonie bosonie self-requested a review March 3, 2024 22:50
Copy link
Collaborator

@bosonie bosonie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! Thanks @sphuber

@sphuber sphuber merged commit 340dbee into master Mar 4, 2024
13 checks passed
@sphuber sphuber deleted the fix/install-optional-requirements branch March 4, 2024 07:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants