Skip to content

Commit

Permalink
Merge pull request #1199 from WilliamJamieson/feature/remove-pkg_reso…
Browse files Browse the repository at this point in the history
…urces
  • Loading branch information
WilliamJamieson authored Oct 11, 2022
2 parents f889d84 + f954155 commit 920bf45
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 60 deletions.
10 changes: 8 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ jobs:
default_python: '3.10'
envs: |
- linux: coverage
name: Python 3.10 coverage
python-version: 3.10
- linux: coverage
name: Python 3.9 coverage
python-version: 3.9
- linux: coverage
name: Python 3.8 coverage
python-version: 3.8
coverage: codecov

test:
Expand All @@ -49,8 +57,6 @@ jobs:
# Any env name which does not start with `pyXY` will use this Python version.
default_python: '3.9'
envs: |
- linux: py38
- linux: py39
- macos: py39
- windows: py39
Expand Down
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
`~asdf.search.AsdfSearchResult.schema_info` method. [#1197]
- Use forc ndarray flag to correctly test for fortran array contiguity [#1206]
- Unpin ``jsonschema`` version and fix ``jsonschema`` deprecation warnings. [#1185]
- Replace ``pkg_resources`` with ``importlib.metadata``. [#1199]

2.13.0 (2022-08-19)
-------------------
Expand Down
4 changes: 2 additions & 2 deletions asdf/asdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import numpy as np
from jsonschema import ValidationError
from pkg_resources import parse_version
from packaging.version import Version

from . import _display as display
from . import _node_info as node_info
Expand Down Expand Up @@ -336,7 +336,7 @@ def _check_extensions(self, tree, strict=False):
if installed.package_version is None or installed.package_name != extension.software["name"]:
continue
# Compare version in file metadata with installed version
if parse_version(installed.package_version) < parse_version(extension.software["version"]):
if Version(installed.package_version) < Version(extension.software["version"]):
msg = ("File {}was created with extension {}, but older package ({}=={}) " "is installed.").format(
filename,
extension_description,
Expand Down
16 changes: 9 additions & 7 deletions asdf/entry_points.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import sys
import warnings

from pkg_resources import iter_entry_points
if sys.version_info < (3, 10):
from importlib_metadata import entry_points
else:
from importlib.metadata import entry_points

from .exceptions import AsdfWarning
from .extension import ExtensionProxy
Expand All @@ -25,21 +29,19 @@ def get_extensions():
def _list_entry_points(group, proxy_class):
results = []

entry_points = list(iter_entry_points(group=group))
points = entry_points(group=group)

# The order of plugins may be significant, since in the case of
# duplicate functionality the first plugin in the list takes
# precedence. It's not clear if entry points are ordered
# in a consistent way across systems so we explicitly sort
# by package name. Plugins from this package are placed
# at the end so that other packages can override them.
asdf_entry_points = [e for e in entry_points if e.dist.project_name == "asdf"]
other_entry_points = sorted(
(e for e in entry_points if e.dist.project_name != "asdf"), key=lambda e: e.dist.project_name
)
asdf_entry_points = [e for e in points if e.dist.name == "asdf"]
other_entry_points = sorted((e for e in points if e.dist.name != "asdf"), key=lambda e: e.dist.name)

for entry_point in other_entry_points + asdf_entry_points:
package_name = entry_point.dist.project_name
package_name = entry_point.dist.name
package_version = entry_point.dist.version

def _handle_error(e):
Expand Down
7 changes: 0 additions & 7 deletions asdf/fits_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,10 +351,3 @@ def write_to(

def update(self, all_array_storage=None, all_array_compression=None, pad_blocks=False, **kwargs):
raise NotImplementedError("In-place update is not currently implemented for ASDF-in-FITS")

self._update_asdf_extension(
all_array_storage=all_array_storage,
all_array_compression=all_array_compression,
pad_blocks=pad_blocks,
**kwargs,
)
14 changes: 14 additions & 0 deletions asdf/tests/test_asdf.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
import warnings

import pytest

from asdf import config_context, get_config
from asdf.asdf import AsdfFile, SerializationContext, open_asdf
from asdf.entry_points import get_extensions
from asdf.exceptions import AsdfWarning
from asdf.extension import AsdfExtensionList, ExtensionManager, ExtensionProxy
from asdf.tests.helpers import assert_no_warnings, yaml_to_asdf
from asdf.versioning import AsdfVersion


def test_no_warnings_get_extensions():
"""
Smoke test for changes to the `importlib.metadata` entry points API.
"""

with warnings.catch_warnings():
warnings.simplefilter("error")

get_extensions()


class TestExtension:
__test__ = False

Expand Down
59 changes: 41 additions & 18 deletions asdf/tests/test_entry_points.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import pkg_resources
import sys

import pytest
from pkg_resources import EntryPoint

if sys.version_info < (3, 10):
import importlib_metadata as metadata
else:
import importlib.metadata as metadata

from asdf import entry_points
from asdf._version import version as asdf_package_version
Expand All @@ -16,17 +21,15 @@ def mock_entry_points():

@pytest.fixture(autouse=True)
def monkeypatch_entry_points(monkeypatch, mock_entry_points):
def _iter_entry_points(*, group):
def _entry_points(*, group):
for candidate_group, name, func_name in mock_entry_points:
if candidate_group == group:
yield EntryPoint(
name,
"asdf.tests.test_entry_points",
attrs=(func_name,),
dist=pkg_resources.get_distribution("asdf"),
)
point = metadata.EntryPoint(name=name, group="asdf.tests.test_entry_points", value=func_name)
vars(point).update(dist=metadata.distribution("asdf"))

yield point

monkeypatch.setattr(entry_points, "iter_entry_points", _iter_entry_points)
monkeypatch.setattr(entry_points, "entry_points", _entry_points)


def resource_mappings_entry_point_successful():
Expand All @@ -49,7 +52,13 @@ def resource_mappings_entry_point_bad_element():


def test_get_resource_mappings(mock_entry_points):
mock_entry_points.append(("asdf.resource_mappings", "successful", "resource_mappings_entry_point_successful"))
mock_entry_points.append(
(
"asdf.resource_mappings",
"successful",
"asdf.tests.test_entry_points:resource_mappings_entry_point_successful",
)
)
mappings = entry_points.get_resource_mappings()
assert len(mappings) == 2
for m in mappings:
Expand All @@ -58,13 +67,21 @@ def test_get_resource_mappings(mock_entry_points):
assert m.package_version == asdf_package_version

mock_entry_points.clear()
mock_entry_points.append(("asdf.resource_mappings", "failing", "resource_mappings_entry_point_failing"))
mock_entry_points.append(
("asdf.resource_mappings", "failing", "asdf.tests.test_entry_points:resource_mappings_entry_point_failing")
)
with pytest.warns(AsdfWarning, match="Exception: NOPE"):
mappings = entry_points.get_resource_mappings()
assert len(mappings) == 0

mock_entry_points.clear()
mock_entry_points.append(("asdf.resource_mappings", "bad_element", "resource_mappings_entry_point_bad_element"))
mock_entry_points.append(
(
"asdf.resource_mappings",
"bad_element",
"asdf.tests.test_entry_points:resource_mappings_entry_point_bad_element",
)
)
with pytest.warns(AsdfWarning, match="TypeError: Resource mapping must implement the Mapping interface"):
mappings = entry_points.get_resource_mappings()
assert len(mappings) == 2
Expand Down Expand Up @@ -109,7 +126,9 @@ class FauxLegacyExtension:


def test_get_extensions(mock_entry_points):
mock_entry_points.append(("asdf.extensions", "successful", "extensions_entry_point_successful"))
mock_entry_points.append(
("asdf.extensions", "successful", "asdf.tests.test_entry_points:extensions_entry_point_successful")
)
extensions = entry_points.get_extensions()
assert len(extensions) == 2
for e in extensions:
Expand All @@ -118,21 +137,25 @@ def test_get_extensions(mock_entry_points):
assert e.package_version == asdf_package_version

mock_entry_points.clear()
mock_entry_points.append(("asdf.extensions", "failing", "extensions_entry_point_failing"))
mock_entry_points.append(
("asdf.extensions", "failing", "asdf.tests.test_entry_points:extensions_entry_point_failing")
)
with pytest.warns(AsdfWarning, match="Exception: NOPE"):
extensions = entry_points.get_extensions()
assert len(extensions) == 0

mock_entry_points.clear()
mock_entry_points.append(("asdf.extensions", "bad_element", "extensions_entry_point_bad_element"))
mock_entry_points.append(
("asdf.extensions", "bad_element", "asdf.tests.test_entry_points:extensions_entry_point_bad_element")
)
with pytest.warns(
AsdfWarning, match="TypeError: Extension must implement the Extension or AsdfExtension interface"
):
extensions = entry_points.get_extensions()
assert len(extensions) == 2

mock_entry_points.clear()
mock_entry_points.append(("asdf_extensions", "legacy", "LegacyExtension"))
mock_entry_points.append(("asdf_extensions", "legacy", "asdf.tests.test_entry_points:LegacyExtension"))
extensions = entry_points.get_extensions()
assert len(extensions) == 1
for e in extensions:
Expand All @@ -142,7 +165,7 @@ def test_get_extensions(mock_entry_points):
assert e.legacy is True

mock_entry_points.clear()
mock_entry_points.append(("asdf_extensions", "failing", "FauxLegacyExtension"))
mock_entry_points.append(("asdf_extensions", "failing", "asdf.tests.test_entry_points:FauxLegacyExtension"))
with pytest.warns(AsdfWarning, match="TypeError"):
extensions = entry_points.get_extensions()
assert len(extensions) == 0
17 changes: 17 additions & 0 deletions asdf/tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,20 @@ def read(self, size=-1):
fd = generic_io.get_file(OnlyHasAReadMethod(content))
assert util.get_file_type(fd) == expected_type
assert fd.read() == content


def test_minversion():
import numpy
import yaml

good_versions = ["1.16", "1.16.1", "1.16.0.dev", "1.16dev"]
bad_versions = ["100000", "100000.2rc1"]
for version in good_versions:
assert util.minversion(numpy, version)
assert util.minversion("numpy", version)
for version in bad_versions:
assert not util.minversion(numpy, version)
assert not util.minversion("numpy", version)

assert util.minversion(yaml, "3.1")
assert util.minversion("yaml", "3.1")
47 changes: 26 additions & 21 deletions asdf/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,23 @@
import math
import re
import struct
import sys
import types
from functools import lru_cache
from importlib import metadata
from urllib.request import pathname2url

import numpy as np
from packaging.version import Version

from . import constants

if sys.version_info < (3, 10):
from importlib_metadata import packages_distributions
else:
from importlib.metadata import packages_distributions


# We're importing our own copy of urllib.parse because
# we need to patch it to support asdf:// URIs, but it'd
# be irresponsible to do this for all users of a
Expand Down Expand Up @@ -323,14 +332,12 @@ def get_class_name(obj, instance=True):
return _CLASS_NAME_OVERRIDES.get(class_name, class_name)


def minversion(module, version, inclusive=True, version_path="__version__"):
def minversion(module, version, inclusive=True):
"""
Returns `True` if the specified Python module satisfies a minimum version
requirement, and `False` if not.
By default this uses `pkg_resources.parse_version` to do the version
comparison if available. Otherwise it falls back on
`packaging.version.Version`.
Copied from astropy.utils.misc.minversion to avoid dependency on astropy.
Parameters
----------
Expand All @@ -347,17 +354,14 @@ def minversion(module, version, inclusive=True, version_path="__version__"):
inclusive : `bool`
The specified version meets the requirement inclusively (i.e. ``>=``)
as opposed to strictly greater than (default: `True`).
version_path : `str`
A dotted attribute path to follow in the module for the version.
Defaults to just ``'__version__'``, which should work for most Python
modules.
"""

if isinstance(module, types.ModuleType):
module_name = module.__name__
module_version = getattr(module, "__version__", None)
elif isinstance(module, str):
module_name = module
module_version = None
try:
module = resolve_name(module_name)
except ImportError:
Expand All @@ -366,23 +370,24 @@ def minversion(module, version, inclusive=True, version_path="__version__"):
raise ValueError(
"module argument must be an actual imported "
"module, or the import name of the module; "
"got {!r}".format(module)
f"got {repr(module)}"
)

if "." not in version_path:
have_version = getattr(module, version_path)
else:
have_version = resolve_name(".".join([module.__name__, version_path]))

try:
from pkg_resources import parse_version
except ImportError:
from packaging.version import Version as parse_version
if module_version is None:
try:
module_version = metadata.version(module_name)
except metadata.PackageNotFoundError:
# Maybe the distribution name is different from package name.
# Calling packages_distributions is costly so we do it only
# if necessary, as only a few packages don't have the same
# distribution name.
dist_names = packages_distributions()
module_version = metadata.version(dist_names[module_name][0])

if inclusive:
return parse_version(have_version) >= parse_version(version)
return Version(module_version) >= Version(version)
else:
return parse_version(have_version) > parse_version(version)
return Version(module_version) > Version(version)


class InheritDocstrings(type):
Expand Down
9 changes: 7 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
from pathlib import Path

import tomli
from pkg_resources import get_distribution
from sphinx_asdf.conf import * # noqa: F403, F401

try:
from importlib.metadata import distribution
except ImportError:
from importlib_metadata import distribution


# Get configuration information from `pyproject.toml`
with open(Path(__file__).parent.parent / "pyproject.toml", "rb") as configuration_file:
conf = tomli.load(configuration_file)
Expand All @@ -14,7 +19,7 @@
author = f"{configuration['authors'][0]['name']} <{configuration['authors'][0]['email']}>"
copyright = f"{datetime.datetime.now().year}, {configuration['authors'][0]['name']}"

release = get_distribution(configuration["name"]).version
release = distribution(configuration["name"]).version
# for example take major/minor
version = ".".join(release.split(".")[:2])

Expand Down
Loading

0 comments on commit 920bf45

Please sign in to comment.