Skip to content

Commit

Permalink
feat: add dist logic and version conflict
Browse files Browse the repository at this point in the history
  • Loading branch information
Rodrigo-Novas authored and ocelotl committed Sep 4, 2024
1 parent bf10494 commit 99502c9
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 21 deletions.
1 change: 0 additions & 1 deletion opentelemetry-instrumentation/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.4",
"setuptools >= 16.0",
"wrapt >= 1.0.0, < 2.0.0",
]

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 importlib.metadata import entry_points
from importlib.metadata import entry_points, EntryPoint, distributions

from opentelemetry.instrumentation.dependencies import (
get_dist_dependency_conflicts,
Expand All @@ -31,6 +31,29 @@
_logger = getLogger(__name__)


class _EntryPointDistFinder:
def __int__(self):
self._mapping = None

def dist_for(self, entry_point: EntryPoint):
dist = getattr(entry_point, "dist", None)
if dist:
return dist

if self._mapping is None:
self._mapping = {
self._key_for(ep): dist
for ep in dist.entry_points
for dist in distributions()
}

return self._mapping.get(self._key_for(entry_point))

@staticmethod
def _key_for(entry_point: EntryPoint):
return f"{entry_point.group}:{entry_point.name}:{entry_point.value}"


def _load_distro() -> BaseDistro:
distro_name = environ.get(OTEL_PYTHON_DISTRO, None)
for entry_point in entry_points().get("opentelemetry_distro", []):
Expand Down Expand Up @@ -58,6 +81,7 @@ def _load_distro() -> BaseDistro:

def _load_instrumentors(distro):
package_to_exclude = environ.get(OTEL_PYTHON_DISABLED_INSTRUMENTATIONS, [])
entry_point_finder = _EntryPointDistFinder()
if isinstance(package_to_exclude, str):
package_to_exclude = package_to_exclude.split(",")
# to handle users entering "requests , flask" or "requests, flask" with spaces
Expand All @@ -74,7 +98,8 @@ def _load_instrumentors(distro):
continue

try:
conflict = get_dist_dependency_conflicts(entry_point.dist)
entry_point_dist = entry_point_finder.dist_for(entry_point)
conflict = get_dist_dependency_conflicts(entry_point_dist)
if conflict:
_logger.debug(
"Skipping instrumentation %s: %s",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
check_call,
)

from importlib.metadata import distribution, PackageNotFoundError
from packaging.requirements import Requirement
from importlib.metadata import PackageNotFoundError, version

from opentelemetry.instrumentation.bootstrap_gen import (
default_instrumentations,
Expand Down Expand Up @@ -91,13 +92,21 @@ def _pip_check():


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

try:
distribution(req)
dist_version = version(req.name)
except PackageNotFoundError:
return False

if not req.specifier.filter(dist_version):
logger.warning(
"instrumentation for package %s is available"
" but version %s is installed. Skipping.",
req,
dist_version
)
return False
return True


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

from importlib.metadata import distribution, PackageNotFoundError, Distribution
from packaging.requirements import Requirement
from importlib.metadata import PackageNotFoundError, Distribution, requires, version

logger = getLogger(__name__)

Expand All @@ -21,9 +22,9 @@ def __str__(self):
def get_dist_dependency_conflicts(
dist: Distribution,
) -> Optional[DependencyConflict]:
main_deps = dist.requires()
main_deps = dist.requires
instrumentation_deps = []
for dep in dist.requires(("instruments",)):
for dep in requires(("instruments",)):
if dep not in main_deps:
# we set marker to none so string representation of the dependency looks like
# requests ~= 1.0
Expand All @@ -40,8 +41,11 @@ def get_dependency_conflicts(
deps: Collection[str],
) -> Optional[DependencyConflict]:
for dep in deps:
req = Requirement(dep)
try:
distribution(dep)
dist_version = version(req.name)
except PackageNotFoundError:
return DependencyConflict(dep)
return DependencyConflict(req.name)
if not req.specifier.filter(dist_version):
return DependencyConflict(req.name)
return None
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ 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"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.distributions"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.EntryPoint"
)
def test_load_configurators(
self, iter_mock
Expand Down Expand Up @@ -61,7 +67,13 @@ def test_load_configurators(
"os.environ", {OTEL_PYTHON_CONFIGURATOR: "custom_configurator2"}
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.iter_entry_points"
"opentelemetry.instrumentation.auto_instrumentation._load.entry_points"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.distributions"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.EntryPoint"
)
def test_load_configurators_no_ep(
self, iter_mock
Expand All @@ -74,7 +86,13 @@ 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"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.distributions"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.EntryPoint"
)
def test_load_configurators_error(self, iter_mock):
# Add multiple entry points but only specify the 2nd in the environment variable.
Expand All @@ -101,7 +119,13 @@ 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"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.distributions"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.EntryPoint"
)
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 @@ -134,7 +158,13 @@ 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"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.distributions"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.EntryPoint"
)
def test_load_distro_not_distro(
self, iter_mock, default_distro_mock, isinstance_mock
Expand Down Expand Up @@ -166,7 +196,13 @@ 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"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.distributions"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.EntryPoint"
)
def test_load_distro_no_ep(self, iter_mock, default_distro_mock):
iter_mock.return_value = ()
Expand All @@ -181,7 +217,13 @@ 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"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.distributions"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.EntryPoint"
)
def test_load_distro_error(self, iter_mock, isinstance_mock):
ep_mock1 = Mock()
Expand Down Expand Up @@ -211,7 +253,13 @@ 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"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.distributions"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.EntryPoint"
)
def test_load_instrumentors(self, iter_mock, dep_mock):
# Mock opentelemetry_pre_instrument entry points
Expand Down Expand Up @@ -285,7 +333,13 @@ 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"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.distributions"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.EntryPoint"
)
def test_load_instrumentors_dep_conflict(
self, iter_mock, dep_mock
Expand Down

0 comments on commit 99502c9

Please sign in to comment.