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

Update collection loader for Python 3.10 #76225

Merged
merged 14 commits into from
Dec 1, 2021

Conversation

s-hertel
Copy link
Contributor

@s-hertel s-hertel commented Nov 4, 2021

SUMMARY

Fixes #74660

Implement find_spec and exec_module to remove reliance on deprecated methods find_module and load_module.

ISSUE TYPE
  • Feature Pull Request

…methods in the collection loader

ci_complete
@ansibot ansibot added WIP This issue/PR is a work in progress. Nevertheless it was shared for getting input from peers. affects_2.13 feature This issue/PR relates to a feature request. needs_triage Needs a first human triage before being processed. support:core This issue/PR relates to code supported by the Ansible Engineering Team. test This PR relates to tests. labels Nov 4, 2021
@bcoca bcoca removed the needs_triage Needs a first human triage before being processed. label Nov 4, 2021
Copy link
Member

@nitzmahone nitzmahone left a comment

Choose a reason for hiding this comment

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

Added comments to sivel's; the other piece of this that looks plausible is actually just getting rid of ansible-test's copy of the collection loader. We assumed it wouldn't be very feasible to have a combo 2.x/3.x loader, but it kinda looks like it might be, so if this all works, we should probably (either as part of this PR or right after) get rid of the second copy altogether.

@ansibot ansibot added needs_ci This PR requires CI testing to be performed. Please close and re-open this PR to trigger CI. and removed needs_ci This PR requires CI testing to be performed. Please close and re-open this PR to trigger CI. labels Nov 8, 2021
Remove extra sys.modules handling

Use default module initialization by returning None from loader.create_module

Refactor

ci_complete
@ansibot ansibot added needs_ci This PR requires CI testing to be performed. Please close and re-open this PR to trigger CI. and removed needs_ci This PR requires CI testing to be performed. Please close and re-open this PR to trigger CI. labels Nov 8, 2021
@ansibot

This comment has been minimized.

@s-hertel s-hertel marked this pull request as ready for review November 9, 2021 12:06
@ansibot ansibot added core_review In order to be merged, this PR must follow the core review workflow. and removed WIP This issue/PR is a work in progress. Nevertheless it was shared for getting input from peers. labels Nov 9, 2021
@ansibot ansibot added the needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR. label Nov 10, 2021
try:
from importlib.util import spec_from_loader
except ImportError:
pass
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It shouldn't ever happen, but maybe I should set spec_from_loader to None here, and raise a ValueError in find_spec if it's None.

Copy link
Member

Choose a reason for hiding this comment

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

I'm actually a little surprised pylint doesn't complain about the possibly undefined name here, but if it genuinely doesn't, we're basically getting the behavior we want for free the way the code is now (a NameError on spec_from_loader if someone calls find_spec, which is probably more useful to anyone that cares than the extra effort required for more explicit error handling. So I'd vote for "leave as is" if pylint's not complaining.

@mattclay
Copy link
Member

@s-hertel The Python version filter in ansible-test should be updated to include the collection loader in the tests for target-only Python versions. That can be done by adding lib/ansible/utils/collection_loader to the list of paths here:

def filter_remote_targets(targets): # type: (t.List[TestTarget]) -> t.List[TestTarget]

The main effect of that change will be to include the collection loader in the compile tests for all Python versions.

@samccann
Copy link
Contributor

does it warrant a changelog fragment??

@s-hertel
Copy link
Contributor Author

@samccann Probably! Added one.

@ansibot ansibot added core_review In order to be merged, this PR must follow the core review workflow. needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR. and removed needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR. core_review In order to be merged, this PR must follow the core review workflow. labels Nov 12, 2021
Copy link
Contributor

@samccann samccann left a comment

Choose a reason for hiding this comment

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

changelog fragment lgtm :-)

Copy link
Member

@nitzmahone nitzmahone left a comment

Choose a reason for hiding this comment

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

Looking good! A few extraneous lines, a couple of recommended explicit returns, and a couple of corner cases to talk through, but I think this is pretty much ready to go.

try:
from importlib.util import spec_from_loader
except ImportError:
pass
Copy link
Member

Choose a reason for hiding this comment

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

I'm actually a little surprised pylint doesn't complain about the possibly undefined name here, but if it genuinely doesn't, we're basically getting the behavior we want for free the way the code is now (a NameError on spec_from_loader if someone calls find_spec, which is probably more useful to anyone that cares than the extra effort required for more explicit error handling. So I'd vote for "leave as is" if pylint's not complaining.

lib/ansible/utils/collection_loader/_collection_finder.py Outdated Show resolved Hide resolved
lib/ansible/utils/collection_loader/_collection_finder.py Outdated Show resolved Hide resolved
Remove get_filename

short-circuit exec_module if it's a redirect

ci_complete
@ansibot ansibot added needs_ci This PR requires CI testing to be performed. Please close and re-open this PR to trigger CI. core_review In order to be merged, this PR must follow the core review workflow. and removed needs_ci This PR requires CI testing to be performed. Please close and re-open this PR to trigger CI. needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR. labels Nov 30, 2021
Copy link
Member

@nitzmahone nitzmahone left a comment

Choose a reason for hiding this comment

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

Looks great- thanks!

@nitzmahone nitzmahone merged commit b50f16d into ansible:devel Dec 1, 2021
@s-hertel
Copy link
Contributor Author

s-hertel commented Dec 1, 2021

Thanks for all the reviews!

Copy link
Member

@webknjaz webknjaz left a comment

Choose a reason for hiding this comment

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

I forgot to publish this draft review. So posting it post-merge for history. In case somebody will want to refactor these things.

Comment on lines +312 to +318
if finder is not None:
if toplevel_pkg == 'ansible_collections':
return finder.find_spec(fullname, path=[self._pathctx])
else:
# call py2's internal loader
return pkgutil.ImpImporter(self._pathctx).find_module(fullname)
return finder.find_spec(fullname)
else:
return None
Copy link
Member

Choose a reason for hiding this comment

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

nitpick: I think this could have less nesting.

Suggested change
if finder is not None:
if toplevel_pkg == 'ansible_collections':
return finder.find_spec(fullname, path=[self._pathctx])
else:
# call py2's internal loader
return pkgutil.ImpImporter(self._pathctx).find_module(fullname)
return finder.find_spec(fullname)
else:
return None
if finder is None:
return None
if toplevel_pkg == 'ansible_collections':
return finder.find_spec(fullname, path=[self._pathctx])
return finder.find_spec(fullname)

Comment on lines +234 to +240
if loader:
spec = spec_from_loader(fullname, loader)
if spec is not None and hasattr(loader, '_subpackage_search_paths'):
spec.submodule_search_locations = loader._subpackage_search_paths
return spec
else:
return None
Copy link
Member

Choose a reason for hiding this comment

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

nit: this is a good place to use a "guard expression"

Suggested change
if loader:
spec = spec_from_loader(fullname, loader)
if spec is not None and hasattr(loader, '_subpackage_search_paths'):
spec.submodule_search_locations = loader._subpackage_search_paths
return spec
else:
return None
if not loader:
return None
spec = spec_from_loader(fullname, loader)
if spec is not None and hasattr(loader, '_subpackage_search_paths'):
spec.submodule_search_locations = loader._subpackage_search_paths
return spec

@felixfontein
Copy link
Contributor

This PR seems to cause some problems in some community.general unit tests for Python 3.7 to 3.9: https://dev.azure.com/ansible/community.general/_build/results?buildId=30854&view=logs&j=83b878b7-b763-5cdf-bf1e-fd8bdd476fa6&t=450760e6-4f51-5fd0-aaa0-1d38d376b06c

@felixfontein
Copy link
Contributor

_______________ ERROR collecting cloud/linode/test_linode_v4.py ________________
02:10 /usr/local/lib/python3.9/dist-packages/pkg_resources/__init__.py:2194: in _handle_ns
02:10     loader = importer.find_spec(packageName).loader
02:10 E   AttributeError: 'NoneType' object has no attribute 'loader'
02:10 
02:10 During handling of the above exception, another exception occurred:
02:10 tests/unit/plugins/modules/cloud/linode/test_linode_v4.py:10: in <module>
02:10     linode_apiv4 = pytest.importorskip('linode_api4')
02:10 /usr/local/lib/python3.9/dist-packages/linode_api4/__init__.py:3: in <module>
02:10     from linode_api4.linode_client import LinodeClient
02:10 /usr/local/lib/python3.9/dist-packages/linode_api4/linode_client.py:7: in <module>
02:10     import pkg_resources
02:10 /usr/local/lib/python3.9/dist-packages/pkg_resources/__init__.py:3243: in <module>
02:10     def _initialize_master_working_set():
02:10 /usr/local/lib/python3.9/dist-packages/pkg_resources/__init__.py:3226: in _call_aside
02:10     f(*args, **kwargs)
02:10 /usr/local/lib/python3.9/dist-packages/pkg_resources/__init__.py:3268: in _initialize_master_working_set
02:10     tuple(
02:10 /usr/local/lib/python3.9/dist-packages/pkg_resources/__init__.py:3269: in <genexpr>
02:10     dist.activate(replace=False)
02:10 /usr/local/lib/python3.9/dist-packages/pkg_resources/__init__.py:2783: in activate
02:10     declare_namespace(pkg)
02:10 /usr/local/lib/python3.9/dist-packages/pkg_resources/__init__.py:2282: in declare_namespace
02:10     _handle_ns(packageName, path_item)
02:10 /usr/local/lib/python3.9/dist-packages/pkg_resources/__init__.py:2199: in _handle_ns
02:10     loader = importer.find_module(packageName)
02:10 /tmp/ansible-test-u4ejf2o8/ansible/utils/collection_loader/_collection_finder.py:303: in find_module
02:10     return finder.find_module(fullname, path=[self._pathctx])
02:10 E   TypeError: _find_module_shim() got an unexpected keyword argument 'path'

@s-hertel
Copy link
Contributor Author

s-hertel commented Dec 2, 2021

@felixfontein I'm looking at this but don't have a fix yet. Thanks for spotting the issue. Running the tests locally fails during getting the metadata, so I'm not sure if my pokings around are going in the right direction (not sure if that's an additional bug or if I'm just running the tests wrong - do you get the same error testing tests/unit/plugins/modules/cloud/linode/test_linode_v4.py locally?). Do you know if there is a way to open a PR against community.general to run CI against an open ansible/ansible PR?

@felixfontein
Copy link
Contributor

@s-hertel there isn't a "ready to use" way to do this; what I usually do is a) kick out almost everything in https://github.com/ansible-collections/community.general/blob/main/.azure-pipelines/azure-pipelines.yml so that only the tests I'm interested are running, and b) modify https://github.com/ansible-collections/community.general/blob/main/tests/utils/shippable/shippable.sh#L60 to install another branch from another user.

@s-hertel
Copy link
Contributor Author

s-hertel commented Dec 2, 2021

@felixfontein Thanks much, good to know 😄 I have a local reproducer now (I was running the test without --docker).

@ansible ansible locked and limited conversation to collaborators Dec 15, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
affects_2.13 core_review In order to be merged, this PR must follow the core review workflow. feature This issue/PR relates to a feature request. support:core This issue/PR relates to code supported by the Ansible Engineering Team. test This PR relates to tests.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Update collection loader for Python 3.10
9 participants