-
Notifications
You must be signed in to change notification settings - Fork 192
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
Factories: do not explicitly check type of entry point if load=False
#5352
Factories: do not explicitly check type of entry point if load=False
#5352
Conversation
Cheers @sphuber, If you look at The problem with checking this fix, is that it only manifests in the |
I see, the difference between those two workflows is that |
This could be problematic: =================================== FAILURES ===================================
________________ test_code_duplicate_non_interactive[vim -cwq] _________________
run_cli_command = <function run_cli_command.<locals>._run_cli_command at 0x7f07fe6cb310>
code = <Code: Remote code 'code' on localhost-test, pk: 1440, uuid: b0b82702-e3cd-42b2-a6f1-5ed3d4aaf35e>
non_interactive_editor = None
@pytest.mark.usefixtures('aiida_profile_clean')
@pytest.mark.parametrize('non_interactive_editor', ('vim -cwq',), indirect=True)
def test_code_duplicate_non_interactive(run_cli_command, code, non_interactive_editor):
"""Test code duplication non-interactive."""
label = 'code_duplicate_noninteractive'
run_cli_command(cmd_code.code_duplicate, ['--non-interactive', f'--label={label}', str(code.pk)])
duplicate = load_code(label)
assert code.description == duplicate.description
assert code.get_prepend_text() == duplicate.get_prepend_text()
assert code.get_append_text() == duplicate.get_append_text()
> assert code.get_input_plugin_name() == duplicate.get_input_plugin_name()
E assert 'core.arithmetic.add' == "EntryPoint(n...alculations')"
E - EntryPoint(name='core.arithmetic.add', value='aiida.calculations.arithmetic.add:ArithmeticAddCalculation', group='aiida.calculations')
E + core.arithmetic.add It seems like perhaps |
I think |
Codecov Report
@@ Coverage Diff @@
## develop #5352 +/- ##
===========================================
- Coverage 82.14% 82.13% -0.00%
===========================================
Files 533 533
Lines 38491 38510 +19
===========================================
+ Hits 31613 31625 +12
- Misses 6878 6885 +7
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
74ff375
to
19f3596
Compare
Its certainly not ideal 😒, but then again I think we would not have to have setuptools installed, if it was not for the pymatgen issue, since build-system dependencies are usually installed in isolated environments |
That's the thing though. I removed the But even if we could technically install without |
I actually cannot reproduce this: aiida_py38) sph@citadel:~/code/aiida/env/release/aiida-core$ ipython
Python 3.8.10 (default, Nov 26 2021, 20:14:08)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.31.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import setuptools
In [2]: setuptools.__version__
Out[2]: '60.9.1'
In [3]: from importlib_metadata import entry_points
In [4]: ep = entry_points().select(group='aiida.calculations', name='core.arithmetic.add')['core.arithmetic.add']
In [5]: type(ep)
Out[5]: setuptools._vendor.importlib_metadata.EntryPoint
In [6]: str(ep)
Out[6]: "EntryPoint(name='core.arithmetic.add', value='aiida.calculations.arithmetic.add:ArithmeticAddCalculation', group='aiida.calculations')" versus (aiida_py38) sph@citadel:~/code/aiida/env/release/aiida-core$ ipython
Python 3.8.10 (default, Nov 26 2021, 20:14:08)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.31.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import setuptools
In [2]: setuptools.__version__
Out[2]: '60.8.0'
In [3]: from importlib_metadata import entry_points
In [4]: ep = entry_points().select(group='aiida.calculations', name='core.arithmetic.add')['core.arithmetic.add']
In [5]: type(ep)
Out[5]: importlib_metadata.EntryPoint
In [6]: str(ep)
Out[6]: "EntryPoint(name='core.arithmetic.add', value='aiida.calculations.arithmetic.add:ArithmeticAddCalculation', group='aiida.calculations')" Seems like the |
I definitely feel though, that you should make a minor change in e.g. |
Well anyhow, we are now seeing the same test failure in ci-code: https://github.com/aiidateam/aiida-core/runs/5215699280?check_suite_focus=true |
I changed I am tempted to also change |
Well it does not do exactly the same as that, since one installs the hard-pinned requirements from the lock files, and one installs the looser pinnings from the |
The recent release of `setuptools==60.9.0` caused the CI to start failing. The `CalculationFactory` started raising, when invoked through the `PluginParamType`, when `load=False` because the loaded entry points were no longer an instance of `importlib_metadata.EntryPoint`. The reason for this change is that as of that `setuptools` release, it started vendoring the `importlib_metadata` package and so it overrides the type to `setuptools._vendor.importlib_metadata.EntryPoint` for the loaded entry points. For reasons unknown, this behavior of the changing of the type of loaded entry points, only manifested when invoking the CLI through `pytest`. When calling the failing CLI command directly, it would work just fine. One solution to this problem would have been to extend the check to also allow the vendored entry point type of `setuptools` but this seems fragile. Really, it seems that the original design of checking the type of the entry point in the case of `load=False` is unnecessary. There is no reason to suspect that `get_entry_point` should return anything that does not behave as an entry point if it didn't except. It is better to be Pythonic here and rely on duck typing. If what is returned by the factory behaves like an `EntryPoint` instance, regardless of its exact module and class, then that is all that matters. The test `tests/plugins/test_entry_point.py` was also explicitly checking the type of the return value of `get_entry_point` but is shouldn't as its purpose is just to verify the deprecation warning is emitted for deprecated entry point names, and so the check is removed.
The `CodeBuilder` could receive the `input_plugin_name` keyword argument as an `EntryPoint` from the `verdi code setup/duplicate` commands, so it included a switch to get the entry point name from it. Due to a bug in setuptools, the type of the `EntryPoint` can change under certain circumstances and so the `isinstance` check on the entry point does not hit. This causes the `EntryPoint` to be passed to the `Code.set_input_plugin_name` method, which does not check the type and casts the argument to `str`. However, the `__str__` of `EntryPoint` does not return just the name, but a more verbose representation. This will cause the `Code` to be unusable. The conversion of the `EntryPoint` to its name in the `CodeBuilder is removed and instead the CLI commands are responsible for passing the string entry point name. Ideally, `Code.set_input_plugin_name` is updated to only accept `str` arguments and remove the cast. But this would be a breaking change and so is not done for now.
f5b7579
to
1f99246
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me 👍
Just gonna copy this comment here, from #5330, for prosperity before I delete it there:
Cheers for hunting this down @chrisjsewell . It seems to make sense, but I still cannot fully reproduce it, and it seems your fix also didn't do it as the tests are still hanging at that I created a Python 3.8 environment and installed a version of This is behavior with old version (aiida_py38) sph@citadel:~/code/aiida/env/release/aiida-core$ ipython
Python 3.8.10 (default, Nov 26 2021, 20:14:08)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.31.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import setuptools
In [2]: setuptools.__version__
Out[2]: '60.8.0'
In [3]: from aiida.plugins.entry_point import get_entry_point
...: ep = get_entry_point('aiida.calculations', 'core.arithmetic.add')
...: print(type(ep), ep.__module__, ep.__class__)
<class 'importlib_metadata.EntryPoint'> importlib_metadata <class 'importlib_metadata.EntryPoint'>
In [4]: from aiida.plugins import CalculationFactory
In [5]: CalculationFactory('core.arithmetic.add')
Out[5]: aiida.calculations.arithmetic.add.ArithmeticAddCalculation We get the class that we expect. And this is with the latest release (aiida_py38) sph@citadel:~/code/aiida/env/release/aiida-core$ ipython
Python 3.8.10 (default, Nov 26 2021, 20:14:08)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.31.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import setuptools
In [2]: setuptools.__version__
Out[2]: '60.9.1'
In [3]: from aiida.plugins.entry_point import get_entry_point
...: ep = get_entry_point('aiida.calculations', 'core.arithmetic.add')
...: print(type(ep), ep.__module__, ep.__class__)
<class 'setuptools._vendor.importlib_metadata.EntryPoint'> setuptools._vendor.importlib_metadata <class 'setuptools._vendor.importlib_metadata.EntryPoint'>
In [4]: from aiida.plugins import CalculationFactory
In [5]: CalculationFactory('core.arithmetic.add')
Out[5]: aiida.calculations.arithmetic.add.ArithmeticAddCalculation Then I tried running the tests, and there I can reproduce it locally. That is when I run So conclusion so far: the problem only appears for:
Honestly no idea what could cause the difference with I also note that the bug appears when running just in (aiida_py38) sph@citadel:~/code/aiida/env/release/aiida-core$ ipython
Python 3.8.10 (default, Nov 26 2021, 20:14:08)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.31.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import setuptools
In [2]: setuptools.__version__
Out[2]: '60.9.1'
In [3]: from aiida.plugins import CalculationFactory
In [4]: ep = CalculationFactory('core.arithmetic.add', load=False)
HERE False core.arithmetic.add <class 'setuptools._vendor.importlib_metadata.EntryPoint'>
---------------------------------------------------------------------------
InvalidEntryPointTypeError Traceback (most recent call last)
<ipython-input-4-1fbbfceb5d72> in <module>
----> 1 ep = CalculationFactory('core.arithmetic.add', load=False)
~/code/aiida/env/release/aiida-core/aiida/plugins/factories.py in CalculationFactory(entry_point_name, load)
86 return entry_point
87
---> 88 raise_invalid_type_error(entry_point_name, entry_point_group, valid_classes)
89
90
~/code/aiida/env/release/aiida-core/aiida/plugins/factories.py in raise_invalid_type_error(entry_point_name, entry_point_group, valid_classes)
42 template = 'entry point `{}` registered in group `{}` is invalid because its type is not one of: {}'
43 args = (entry_point_name, entry_point_group, ', '.join([e.__name__ for e in valid_classes]))
---> 44 raise InvalidEntryPointTypeError(template.format(*args))
45
46
InvalidEntryPointTypeError: entry point `core.arithmetic.add` registered in group `aiida.calculations` is invalid because its type is not one of: CalcJob, calcfunction So the real question might be why running this same code path through invoking
|
The recent release of
setuptools==60.9.0
caused the CI to startfailing. The
CalculationFactory
started raising, when invoked throughthe
PluginParamType
, whenload=False
because the loaded entry pointswere no longer an instance of
importlib_metadata.EntryPoint
. Thereason for this change is that as of that
setuptools
release, itstarted vendoring the
importlib_metadata
package and so it overridesthe type to
setuptools._vendor.importlib_metadata.EntryPoint
for theloaded entry points.
For reasons unknown, this behavior of the changing of the type of loaded
entry points, only manifested when invoking the CLI through
pytest
.When calling the failing CLI command directly, it would work just fine.
One solution to this problem would have been to extend the check to also
allow the vendored entry point type of
setuptools
but this seemsfragile. Really, it seems that the original design of checking the type
of the entry point in the case of
load=False
is unnecessary. There isno reason to suspect that
get_entry_point
should return anything thatdoes not behave as an entry point if it didn't except. It is better to
be Pythonic here and rely on duck typing. If what is returned by the
factory behaves like an
EntryPoint
instance, regardless of its exactmodule and class, then that is all that matters.