Skip to content

ENH: Use importlib in minversion#11714

Merged
pllim merged 6 commits intoastropy:mainfrom
pllim:utils-minversion-importlib
Sep 8, 2021
Merged

ENH: Use importlib in minversion#11714
pllim merged 6 commits intoastropy:mainfrom
pllim:utils-minversion-importlib

Conversation

@pllim
Copy link
Member

@pllim pllim commented May 6, 2021

Description

This pull request is to simplify minversion code to use importlib.metadata.version that Python provides.

Fixes #11705

@pllim pllim added Refactoring API change PRs and issues that change an existing API, possibly requiring a deprecation period labels May 6, 2021
@pllim pllim added this to the v5.0 milestone May 6, 2021
@pllim pllim requested review from embray, mhvk and saimn May 6, 2021 22:11
@github-actions github-actions bot added the utils label May 6, 2021
@github-actions
Copy link
Contributor

github-actions bot commented May 6, 2021

👋 Thank you for your draft pull request! Do you know that you can use [ci skip] or [skip ci] in your commit messages to skip running continuous integration tests until you are ready?

@pllim pllim force-pushed the utils-minversion-importlib branch from 8841ce6 to 962f99d Compare May 6, 2021 22:14
test_module.__version__ = '0.12.2'
good_versions = ['0.12', '0.12.1', '0.12.0.dev', '0.12dev']
bad_versions = ['1', '1.2rc1']
import numpy
Copy link
Member Author

Choose a reason for hiding this comment

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

I could not use a fake module anymore with this change, but I did test edge case manually. If there is a way to pass in fake module again, please let me know. Thanks!

With this patch:

>>> from astropy.utils import minversion
>>> import numpy
>>> numpy.__version__
'1.20.2'
>>> minversion(numpy, '1.20.2')
True
>>> minversion(numpy, '1.20.2', inclusive=False)
False

@pllim pllim marked this pull request as ready for review May 6, 2021 22:23
@pllim

This comment has been minimized.

Copy link
Contributor

@mhvk mhvk left a comment

Choose a reason for hiding this comment

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

Wow, I had looked at this and had thought maybe to just add an extra layer, but you just went what does seem the right route, and hacked it all out. I like it! Just an in-line comment about how to get importlib.metadata on python 3.7

import sys
import types
import importlib
import importlib.metadata
Copy link
Contributor

Choose a reason for hiding this comment

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

importlib.metadata requires python 3.8, so this needs to be

try:
    from importlib import metadata
except ImportError:
    import importlib_metadata as metadata

(Unless we drop python 3.7, which would be borderline: it was released June 27, 2018, so we're supposed to support 42 months, i.e., until Dec 27, 2021...)

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, I knew I missed something. Thanks!

bad_versions = ['1', '1.2rc1']
import numpy
good_versions = ['1.16', '1.16.1', '1.16.0.dev', '1.16dev']
bad_versions = ['100000', '100000.2rc1']
Copy link
Member Author

Choose a reason for hiding this comment

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

p.s. Need to update this test when Numpy v100000 is released. 😆

mhvk
mhvk previously approved these changes May 7, 2021
Copy link
Contributor

@mhvk mhvk left a 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! Might still be good for @embray to have a look as well.
EDIT: dismissed my earlier approval since tests are still failing

@mhvk
Copy link
Contributor

mhvk commented May 7, 2021

Well, it may look good but the tests failed quite miserably!?

@mhvk
Copy link
Contributor

mhvk commented May 7, 2021

Hmm, the problem is that metadata.version expects a distribution name, but for yaml that is pyyaml, not yaml. It is quite unclear how to go backwards from that...

@mhvk mhvk self-requested a review May 7, 2021 01:37
@mhvk mhvk dismissed their stale review May 7, 2021 01:38

Tests are still failing, this would imply larger API change

@saimn
Copy link
Contributor

saimn commented May 7, 2021

Indeed, for some packages (pyerfa too) the import name is not the package name ...

@mhvk
Copy link
Contributor

mhvk commented May 7, 2021

@saimn - any suggestion on how to get the package name from the module? Or perhaps we should stick with supporting __version__ as well?

@saimn
Copy link
Contributor

saimn commented May 7, 2021

It seems a function was added recently to do just this: packages_distributions()
https://importlib-metadata.readthedocs.io/en/stable/using.html#package-distributions
But I guess it will be in Python 3.10, so we would have to require the backport package for all Pythons < 3.10

Otherwise I found a way with dist.read_text('top_level.txt') and wasn't sure if it was a hack or not. packages_distributions() uses that (https://github.com/python/importlib_metadata/pull/287/files), so I guess that's a solution.

@pllim
Copy link
Member Author

pllim commented May 7, 2021

Does this mean switching to importlib here is currently pre-mature? We can always wait a few more releases.

@saimn saimn mentioned this pull request Jul 20, 2021
9 tasks
module_version = metadata.version(module_name)
if inclusive:
return LooseVersion(have_version) >= LooseVersion(version)
return module_version >= version
Copy link

Choose a reason for hiding this comment

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

importlib.metadata.version returns a string, not a version object like LooseVersion or packaging.version.Version. Strings don't compare well as versions, e.g. '3.10' < '3.9'.

@pllim
Copy link
Member Author

pllim commented Aug 5, 2021

This has languished too long. I don't remember what is the status anymore...

@saimn
Copy link
Contributor

saimn commented Sep 6, 2021

We would need a function similar to packages_distributions mentioned above, we could just copy it from https://github.com/python/importlib_metadata/pull/287/files (it's only 5 lines) until 3.10 is our minimum version.
And @encukou is right, version string should be parsed with packaging.version.Version before being compared.

@pllim
Copy link
Member Author

pllim commented Sep 7, 2021

I am not sure if I have time... @saimn or @encukou , do you want to submit a new PR to supersede this?

@saimn saimn force-pushed the utils-minversion-importlib branch from 0b716af to 3f740e3 Compare September 8, 2021 17:08
@saimn
Copy link
Contributor

saimn commented Sep 8, 2021

@pllim - I pushed a new commit to your branch with the changes mentioned above. One drawback with packages_distributions is that it is a bit slow, so we may want to cache it's output.

Copy link
Contributor

@mhvk mhvk left a comment

Choose a reason for hiding this comment

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

The need for the function and the cache are not quite as optimal as hoped (see suggestion inline for removing the cache), but I think it is definitely good to get rid of any use of distutils. And it seems that now we gotten as far as we did, we may as well go with it.

m = re.match(expr, have_version)
if m:
have_version = m.group(0)
# Convert the module name to a distribution name
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we mostly do not need this dict, since for most packages the name and distribution name are the same. So, might it make sense to generally avoid creating it by just doing something like:

try:
    module_version  = metadata.version(module_name)
except metadata.PackageNotFoundError:  # module name different from package name?
    module_version = metadata.version(packages_distributions()[module_name][0])

Copy link
Contributor

Choose a reason for hiding this comment

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

Good idea 👍

Copy link
Member Author

@pllim pllim left a comment

Choose a reason for hiding this comment

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

I cannot approve my own PR but the updates LGTM. Thank you very much, @saimn !

Copy link
Contributor

@mhvk mhvk left a 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 too!

@mhvk
Copy link
Contributor

mhvk commented Sep 8, 2021

Code coverage we can ignore, so fine to merge once the last test has passed.

@pllim pllim merged commit 9ebf553 into astropy:main Sep 8, 2021
@pllim pllim deleted the utils-minversion-importlib branch September 8, 2021 20:32
@pllim
Copy link
Member Author

pllim commented Sep 8, 2021

Thanks, all!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API change PRs and issues that change an existing API, possibly requiring a deprecation period Ready-for-final-review Refactoring utils

Projects

None yet

Development

Successfully merging this pull request may close these issues.

utils.minversion should use importlib.metadata to get version info

4 participants