Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Fix version of Flask dependency `werkzeug`
([#1980](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1980))
- `opentelemetry-resource-detector-azure` Using new Cloud Resource ID attribute.
([#1976](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1976))

- `opentelemetry-instrumentation`, `opentelemetry-instrumentation-aiohttp-client` Use importlib-metadata for entry points instead of pkg_resources
([#2124](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2124))

## Version 1.20.0/0.41b0 (2023-09-01)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@

import aiohttp
import aiohttp.test_utils
import importlib_metadata
import yarl
from http_server_mock import HttpServerMock
from pkg_resources import iter_entry_points

from opentelemetry import context
from opentelemetry import trace as trace_api
Expand Down Expand Up @@ -574,8 +574,8 @@ def response_hook(

class TestLoadingAioHttpInstrumentor(unittest.TestCase):
def test_loading_instrumentor(self):
entry_points = iter_entry_points(
"opentelemetry_instrumentor", "aiohttp-client"
entry_points = importlib_metadata.entry_points(
group="opentelemetry_instrumentor", name="aiohttp-client"
)

instrumentor = next(entry_points).load()()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright The OpenTelemetry Authors
# Copyright The Open Telemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -11,7 +11,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import pkg_resources

# IMPORTANT: Only the wsgi module needs this because it is always the first
# package that uses the `{rootdir}/*/tests/` path and gets installed by
Expand All @@ -20,4 +19,5 @@
# Naming the tests module as a namespace package ensures that
# relative imports will resolve properly for subsequent test packages,
# as it enables searching for a composite of multiple test modules.
pkg_resources.declare_namespace(__name__)

# No need for additional code here for Python 3.3+
8 changes: 3 additions & 5 deletions opentelemetry-distro/tests/test_distro.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@

import os
from unittest import TestCase

from pkg_resources import DistributionNotFound, require
import importlib_metadata

from opentelemetry.distro import OpenTelemetryDistro
from opentelemetry.environment_variables import (
Expand All @@ -25,12 +24,11 @@
)
from opentelemetry.sdk.environment_variables import OTEL_EXPORTER_OTLP_PROTOCOL


class TestDistribution(TestCase):
def test_package_available(self):
try:
require(["opentelemetry-distro"])
except DistributionNotFound:
importlib_metadata.distribution("opentelemetry-distro")
except importlib_metadata.PackageNotFoundError:
self.fail("opentelemetry-distro not installed")

def test_default_configuration(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from re import sub
from shutil import which

from pkg_resources import iter_entry_points
from importlib_metadata import entry_points

from opentelemetry.instrumentation.version import __version__

Expand Down Expand Up @@ -50,8 +50,8 @@ def run() -> None:

argument_otel_environment_variable = {}

for entry_point in iter_entry_points(
"opentelemetry_environment_variables"
for entry_point in entry_points(
group="opentelemetry_environment_variables"
):
environment_variable_module = entry_point.load()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from logging import getLogger
from os import environ

from pkg_resources import iter_entry_points
from importlib_metadata import entry_points

from opentelemetry.instrumentation.dependencies import (
get_dist_dependency_conflicts,
Expand All @@ -33,7 +33,7 @@

def _load_distro() -> BaseDistro:
distro_name = environ.get(OTEL_PYTHON_DISTRO, None)
for entry_point in iter_entry_points("opentelemetry_distro"):
for entry_point in entry_points(group="opentelemetry_distro"):
try:
# If no distro is specified, use first to come up.
if distro_name is None or distro_name == entry_point.name:
Expand Down Expand Up @@ -63,10 +63,10 @@ def _load_instrumentors(distro):
# to handle users entering "requests , flask" or "requests, flask" with spaces
package_to_exclude = [x.strip() for x in package_to_exclude]

for entry_point in iter_entry_points("opentelemetry_pre_instrument"):
for entry_point in entry_points(group="opentelemetry_pre_instrument"):
entry_point.load()()

for entry_point in iter_entry_points("opentelemetry_instrumentor"):
for entry_point in entry_points(group="opentelemetry_instrumentor"):
if entry_point.name in package_to_exclude:
_logger.debug(
"Instrumentation skipped for library %s", entry_point.name
Expand All @@ -90,14 +90,14 @@ def _load_instrumentors(distro):
_logger.exception("Instrumenting of %s failed", entry_point.name)
raise exc

for entry_point in iter_entry_points("opentelemetry_post_instrument"):
for entry_point in entry_points(group="opentelemetry_post_instrument"):
entry_point.load()()


def _load_configurators():
configurator_name = environ.get(OTEL_PYTHON_CONFIGURATOR, None)
configured = None
for entry_point in iter_entry_points("opentelemetry_configurator"):
for entry_point in entry_points(group="opentelemetry_configurator"):
if configured is not None:
_logger.warning(
"Configuration of %s not loaded, %s already loaded",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
import subprocess
import sys

import pkg_resources
from importlib_metadata import PackageNotFoundError, distribution
from packaging import version

from opentelemetry.instrumentation.bootstrap_gen import (
default_instrumentations,
Expand Down Expand Up @@ -83,21 +84,45 @@ def _pip_check():
raise RuntimeError(f"Dependency conflict found: {pip_check}")


# def _is_installed(req):
# if req in sys.modules:
# return True

# try:
# dist = distribution(req).version
# except PackageNotFoundError:
# return False
# except pkg_resources.VersionConflict as exc:
# logger.warning(
# "instrumentation for package %s is available but version %s is installed. Skipping.",
# exc.req,
# exc.dist.as_requirement(), # pylint: disable=no-member
# )
# return False
# return True


def _is_installed(req):
if req in sys.modules:
return True

try:
pkg_resources.get_distribution(req)
except pkg_resources.DistributionNotFound:
return False
except pkg_resources.VersionConflict as exc:
logger.warning(
"instrumentation for package %s is available but version %s is installed. Skipping.",
exc.req,
exc.dist.as_requirement(), # pylint: disable=no-member
)
dist = distribution(req)
# Assuming 'req' is in format 'package==version'
# Modify this as per the format of your 'req' string
required_version = req.split("==")[1] if "==" in req else None
if required_version and version.parse(dist.version) != version.parse(
required_version
):
logger.warning(
"Instrumentation for package %s is available but version %s is installed. Skipping.",
req,
dist.version,
)
return False
except PackageNotFoundError:
return False

return True


Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
from logging import getLogger
from typing import Collection, Optional

from pkg_resources import (
Distribution,
DistributionNotFound,
RequirementParseError,
VersionConflict,
get_distribution,
)
import importlib_metadata
from packaging.requirements import Requirement
from packaging.version import parse as parse_version

logger = getLogger(__name__)

Expand All @@ -24,39 +19,55 @@ def __str__(self):
return f'DependencyConflict: requested: "{self.required}" but found: "{self.found}"'


def get_dist_dependency_conflicts(
dist: Distribution,
) -> Optional[DependencyConflict]:
main_deps = dist.requires()
instrumentation_deps = []
for dep in dist.requires(("instruments",)):
if dep not in main_deps:
# we set marker to none so string representation of the dependency looks like
# requests ~= 1.0
# instead of
# requests ~= 1.0; extra = "instruments"
# which does not work with `get_distribution()`
dep.marker = None
instrumentation_deps.append(str(dep))

return get_dependency_conflicts(instrumentation_deps)
def _check_version(conflict_requirement, installed_version):
if not conflict_requirement.specifier.contains(installed_version, prereleases=True):
return f"{conflict_requirement.name} {installed_version}"
return None


def get_dependency_conflicts(
deps: Collection[str],
) -> Optional[DependencyConflict]:
for dep in deps:
try:
get_distribution(dep)
except VersionConflict as exc:
return DependencyConflict(dep, exc.dist)
except DistributionNotFound:
requirement = Requirement(dep)
distribution = importlib_metadata.distribution(requirement.name)
installed_version = parse_version(distribution.version)
conflict_version = _check_version(requirement, installed_version)
if conflict_version:
return DependencyConflict(dep, conflict_version)
except importlib_metadata.PackageNotFoundError:
return DependencyConflict(dep)
except RequirementParseError as exc:
except Exception as exc:
logger.warning(
'error parsing dependency, reporting as a conflict: "%s" - %s',
dep,
exc,
)
return DependencyConflict(dep)
return None


def get_dist_dependency_conflicts(
dist_name: str, # Assuming dist_name is the name of the distribution
) -> Optional[DependencyConflict]:
try:
distribution = importlib_metadata.distribution(dist_name)
except importlib_metadata.PackageNotFoundError:
return DependencyConflict(dist_name)

conflicts = []
for req in distribution.requires or []:
try:
requirement = Requirement(req)
dep_dist = importlib_metadata.distribution(requirement.name)
installed_version = parse_version(dep_dist.version)
if not requirement.specifier.contains(installed_version, prereleases=True):
conflicts.append(f"{requirement.name} {installed_version}")
except importlib_metadata.PackageNotFoundError:
conflicts.append(requirement.name)

if conflicts:
# Return the first conflict found for simplicity
return DependencyConflict(str(conflicts[0]))
return None
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from abc import ABC, abstractmethod
from logging import getLogger

from pkg_resources import EntryPoint
from importlib_metadata import EntryPoint

from opentelemetry.instrumentation.instrumentor import BaseInstrumentor

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class TestLoad(TestCase):
"os.environ", {OTEL_PYTHON_CONFIGURATOR: "custom_configurator2"}
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.iter_entry_points"
"opentelemetry.instrumentation.auto_instrumentation._load.entry_points"
)
def test_load_configurators(self, iter_mock):
# Add multiple entry points but only specify the 2nd in the environment variable.
Expand Down Expand Up @@ -59,7 +59,7 @@ def test_load_configurators(self, iter_mock):
"os.environ", {OTEL_PYTHON_CONFIGURATOR: "custom_configurator2"}
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.iter_entry_points"
"opentelemetry.instrumentation.auto_instrumentation._load.entry_points"
)
def test_load_configurators_no_ep(
self,
Expand All @@ -73,7 +73,7 @@ def test_load_configurators_no_ep(
"os.environ", {OTEL_PYTHON_CONFIGURATOR: "custom_configurator2"}
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.iter_entry_points"
"opentelemetry.instrumentation.auto_instrumentation._load.entry_points"
)
def test_load_configurators_error(self, iter_mock):
# Add multiple entry points but only specify the 2nd in the environment variable.
Expand All @@ -100,7 +100,7 @@ def test_load_configurators_error(self, iter_mock):
"opentelemetry.instrumentation.auto_instrumentation._load.isinstance"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.iter_entry_points"
"opentelemetry.instrumentation.auto_instrumentation._load.entry_points"
)
def test_load_distro(self, iter_mock, isinstance_mock):
# Add multiple entry points but only specify the 2nd in the environment variable.
Expand Down Expand Up @@ -133,7 +133,7 @@ def test_load_distro(self, iter_mock, isinstance_mock):
"opentelemetry.instrumentation.auto_instrumentation._load.DefaultDistro"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.iter_entry_points"
"opentelemetry.instrumentation.auto_instrumentation._load.entry_points"
)
def test_load_distro_not_distro(
self, iter_mock, default_distro_mock, isinstance_mock
Expand Down Expand Up @@ -165,7 +165,7 @@ def test_load_distro_not_distro(
"opentelemetry.instrumentation.auto_instrumentation._load.DefaultDistro"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.iter_entry_points"
"opentelemetry.instrumentation.auto_instrumentation._load.entry_points"
)
def test_load_distro_no_ep(self, iter_mock, default_distro_mock):
iter_mock.return_value = ()
Expand All @@ -180,7 +180,7 @@ def test_load_distro_no_ep(self, iter_mock, default_distro_mock):
"opentelemetry.instrumentation.auto_instrumentation._load.isinstance"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.iter_entry_points"
"opentelemetry.instrumentation.auto_instrumentation._load.entry_points"
)
def test_load_distro_error(self, iter_mock, isinstance_mock):
ep_mock1 = Mock()
Expand Down Expand Up @@ -210,7 +210,7 @@ def test_load_distro_error(self, iter_mock, isinstance_mock):
"opentelemetry.instrumentation.auto_instrumentation._load.get_dist_dependency_conflicts"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.iter_entry_points"
"opentelemetry.instrumentation.auto_instrumentation._load.entry_points"
)
def test_load_instrumentors(self, iter_mock, dep_mock):
# Mock opentelemetry_pre_instrument entry points
Expand Down Expand Up @@ -283,7 +283,7 @@ def test_load_instrumentors(self, iter_mock, dep_mock):
"opentelemetry.instrumentation.auto_instrumentation._load.get_dist_dependency_conflicts"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.iter_entry_points"
"opentelemetry.instrumentation.auto_instrumentation._load.entry_points"
)
def test_load_instrumentors_dep_conflict(self, iter_mock, dep_mock):
ep_mock1 = Mock()
Expand Down
Loading