Skip to content

Commit

Permalink
Use importlib_metadata for 3.8 and 3.9 only
Browse files Browse the repository at this point in the history
  • Loading branch information
ocelotl committed Sep 4, 2024
1 parent d0fb920 commit 67b3367
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 29 deletions.
4 changes: 1 addition & 3 deletions opentelemetry-api/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ classifiers = [
]
dependencies = [
"Deprecated >= 1.2.6",
# FIXME This should be able to be removed after 3.12 is released if there is a reliable API
# in importlib.metadata.
"importlib-metadata >= 6.0, <= 8.4.0",
"importlib-metadata >= 6.0, <= 8.4.0; python_version >= '3.8' and python_version <= '3.9'",
]
dynamic = [
"version",
Expand Down
69 changes: 60 additions & 9 deletions opentelemetry-api/src/opentelemetry/util/_importlib_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,69 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# FIXME: Use importlib.metadata when support for 3.11 is dropped if the rest of
# the supported versions at that time have the same API.
from importlib_metadata import ( # type: ignore
EntryPoint,
EntryPoints,
entry_points,
version,
)

# The importlib-metadata library has introduced breaking changes before to its
# API, this module is kept just to act as a layer between the
# importlib-metadata library and our project if in any case it is necessary to
# do so.

from sys import version_info

if version_info.minor < 10:

# pylint: disable=import-error
from importlib_metadata import version # type: ignore
from importlib_metadata import EntryPoint, EntryPoints
from importlib_metadata import (
entry_points as importlib_metadata_entry_points,
)

else:

from importlib.metadata import EntryPoint, EntryPoints
from importlib.metadata import (
entry_points as importlib_metadata_entry_points,
)
from importlib.metadata import version


def entry_points(**params) -> EntryPoints: # type: ignore
"""
Same as entry_points but requires at least one argument
For Python 3.8 or 3.9:
isinstance(
importlib_metadata.entry_points(), importlib_metadata.EntryPoints
)
evaluates to True.
For Python 3.10, 3.11:
isinstance(
importlib.metadata.entry_points(), importlib.metadata.SelectableGroups
)
evaluates to True.
For Python 3.12:
isinstance(
importlib.metadata.entry_points(), importlib.metadata.EntryPoints
)
evaluates to True.
So, when called with no arguments, entry_points returns objects of
different types depending on the Python version that is being used. This is
obviously very problematic. Nevertheless, in our code we don't ever call
entry_points without arguments, so the approach here is to redefine
entry_points so that it requires at least one argument.
"""

if not params: # type: ignore
raise ValueError("entry_points requires at least one argument")
return importlib_metadata_entry_points(**params) # type: ignore


__all__ = ["entry_points", "version", "EntryPoint", "EntryPoints"]
File renamed without changes.
15 changes: 15 additions & 0 deletions opentelemetry-api/test-requirements-1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
asgiref==3.7.2
Deprecated==1.2.14
iniconfig==2.0.0
packaging==24.0
pluggy==1.5.0
py-cpuinfo==9.0.0
pytest==7.4.4
tomli==2.0.1
typing_extensions==4.10.0
wrapt==1.16.0
zipp==3.19.2
-e opentelemetry-sdk
-e opentelemetry-semantic-conventions
-e tests/opentelemetry-test-utils
-e opentelemetry-api
69 changes: 53 additions & 16 deletions opentelemetry-api/tests/util/test__importlib_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,46 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from sys import version_info
from unittest import TestCase

from opentelemetry.metrics import MeterProvider
from opentelemetry.util._importlib_metadata import EntryPoint, EntryPoints
from opentelemetry.util._importlib_metadata import (
entry_points as importlib_metadata_entry_points,
)
from opentelemetry.util._importlib_metadata import version


class TestDependencies(TestCase):

def test_dependencies(self):

# pylint: disable=import-outside-toplevel
# pylint: disable=unused-import
# pylint: disable=import-error
if version_info.minor < 10:
try:
import importlib_metadata # type: ignore

except ImportError:
self.fail(
"importlib_metadata not installed when testing with "
f"{version_info.major}.{version_info.minor}"
)

else:
try:
import importlib_metadata # noqa

except ImportError:
pass

else:
self.fail(
"importlib_metadata installed when testing with "
f"{version_info.major}.{version_info.minor}"
)


class TestEntryPoints(TestCase):
Expand All @@ -40,27 +73,29 @@ def test_uniform_behavior(self):
"""
Test that entry_points behaves the same regardless of the Python
version.
"""

entry_points = importlib_metadata_entry_points()
self.assertIsInstance(entry_points, EntryPoints)
importlib.metadata was introduced in 3.8 as a replacement for
pkg_resources. The problem is that the API of importlib.metadata
changed in subsequent versions.
entry_points = entry_points.select(group="opentelemetry_propagator")
self.assertIsInstance(entry_points, EntryPoints)
For example, in 3.8 or 3.9 importlib.metadata.entry_points does not
support the keyword arguments group or name, but those keyword
arguments are supported in 3.10, 3.11 and 3.12.
entry_points = entry_points.select(name="baggage")
self.assertIsInstance(entry_points, EntryPoints)
There exists a package named importlib-metadata that has an API that
includes a function named importlib_metadata.entry_points which
supports the keyword arguments group and name, so we use
importlib_metadata.entry_points when running in 3.8 or 3.9.
entry_point = next(iter(entry_points))
self.assertIsInstance(entry_point, EntryPoint)
importlib_metadata.entry_points and importlib.metadata.entry_points do
not return objects of the same type when called without any arguments.
That is why the implementation of the
opentelemetry.util._importlib_metadata redefines entry_points so that
it is mandatory to use an argument.
"""

self.assertEqual(entry_point.name, "baggage")
self.assertEqual(entry_point.group, "opentelemetry_propagator")
self.assertEqual(
entry_point.value,
"opentelemetry.baggage.propagation:W3CBaggagePropagator",
)
with self.assertRaises(ValueError):
importlib_metadata_entry_points()

entry_points = importlib_metadata_entry_points(
group="opentelemetry_propagator"
Expand Down Expand Up @@ -106,3 +141,5 @@ def test_uniform_behavior(self):
entry_points = importlib_metadata_entry_points(group="abc", name="abc")
self.assertIsInstance(entry_points, EntryPoints)
self.assertEqual(len(entry_points), 0)

self.assertIsInstance(version("opentelemetry-api"), str)
5 changes: 4 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,10 @@ commands_pre =

mypy,mypyinstalled: pip install -r {toxinidir}/mypy-requirements.txt

api: pip install -r {toxinidir}/opentelemetry-api/test-requirements.txt
py3{8,9}-test-opentelemetry-api: pip install -r {toxinidir}/opentelemetry-api/test-requirements-0.txt
pypy3-test-opentelemetry-api: pip install -r {toxinidir}/opentelemetry-api/test-requirements-0.txt
py3{10,11,12}-test-opentelemetry-api: pip install -r {toxinidir}/opentelemetry-api/test-requirements-1.txt
lint-opentelemetry-api: pip install -r {toxinidir}/opentelemetry-api/test-requirements-1.txt

sdk: pip install -r {toxinidir}/opentelemetry-sdk/test-requirements.txt
benchmark-opentelemetry-sdk: pip install -r {toxinidir}/opentelemetry-sdk/benchmark-requirements.txt
Expand Down

0 comments on commit 67b3367

Please sign in to comment.