Skip to content
Merged
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
17 changes: 16 additions & 1 deletion .github/.trivyignore
Original file line number Diff line number Diff line change
@@ -1 +1,16 @@
# Empty for now
# =============================
# Accepted Risk Vulnerabilities
# =============================

# Accepting risk due to Python 3.8 support.
CVE-2025-50181 # Requires misconfiguration of urllib3, which agent does not do without intervention
CVE-2025-66418 # Malicious servers could cause high resource consumption
CVE-2025-66471 # Malicious servers could cause high resource consumption
CVE-2026-21441 # Improper Handling of Highly Compressed Data (Data Amplification)

# =======================
# Ignored Vulnerabilities
# =======================

# Not relevant, only affects Pyodide
CVE-2025-50182
2 changes: 1 addition & 1 deletion .github/containers/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ RUN mv "${HOME}/.local/bin/python3.11" "${HOME}/.local/bin/pypy3.11" && \
mv "${HOME}/.local/bin/python3.10" "${HOME}/.local/bin/pypy3.10"

# Install CPython versions
RUN uv python install -f cp3.14 cp3.14t cp3.13 cp3.12 cp3.11 cp3.10 cp3.9
RUN uv python install -f cp3.14 cp3.14t cp3.13 cp3.12 cp3.11 cp3.10 cp3.9 cp3.8

# Set default Python version to CPython 3.13
RUN uv python install -f --default cp3.13
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
python: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]

env:
ASV_FACTOR: "1.1"
Expand Down
13 changes: 4 additions & 9 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ jobs:
matrix:
include:
# Linux glibc
- wheel: cp38-manylinux
os: ubuntu-24.04
- wheel: cp39-manylinux
os: ubuntu-24.04
- wheel: cp310-manylinux
Expand All @@ -39,13 +41,11 @@ jobs:
os: ubuntu-24.04
- wheel: cp313-manylinux
os: ubuntu-24.04
- wheel: cp313t-manylinux
os: ubuntu-24.04
- wheel: cp314-manylinux
os: ubuntu-24.04
- wheel: cp314t-manylinux
os: ubuntu-24.04
# Linux musllibc
- wheel: cp38-musllinux
os: ubuntu-24.04
- wheel: cp39-musllinux
os: ubuntu-24.04
- wheel: cp310-musllinux
Expand All @@ -56,12 +56,8 @@ jobs:
os: ubuntu-24.04
- wheel: cp313-musllinux
os: ubuntu-24.04
- wheel: cp313t-musllinux
os: ubuntu-24.04
- wheel: cp314-musllinux
os: ubuntu-24.04
- wheel: cp314t-musllinux
os: ubuntu-24.04
# Windows
# Windows wheels won't but published until the full release announcement.
# - wheel: cp313-win
Expand Down Expand Up @@ -93,7 +89,6 @@ jobs:
CIBW_ARCHS_MACOS: native
CIBW_ARCHS_WINDOWS: AMD64 ARM64
CIBW_ENVIRONMENT_LINUX: "LD_LIBRARY_PATH=/opt/rh/devtoolset-8/root/usr/lib64:/opt/rh/devtoolset-8/root/usr/lib:/opt/rh/devtoolset-8/root/usr/lib64/dyninst:/opt/rh/devtoolset-8/root/usr/lib/dyninst:/usr/local/lib64:/usr/local/lib"
CIBW_ENABLE: cpython-freethreading
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND_LINUX: "export PYTHONPATH={project}/tests; pytest {project}/tests/agent_unittests -vx"
CIBW_TEST_COMMAND_MACOS: "export PYTHONPATH={project}/tests; pytest {project}/tests/agent_unittests -vx"
Expand Down
4 changes: 2 additions & 2 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Distributed under the following license(s):

## [urllib3](https://pypi.org/project/urllib3)

Copyright (c) 2008-2020 Andrey Petrov and contributors.
Copyright (c) 2008-2019 Andrey Petrov and contributors (see CONTRIBUTORS.txt)

Distributed under the following license(s):

Expand All @@ -46,7 +46,7 @@ Distributed under the following license(s):

## [wrapt](https://pypi.org/project/wrapt)

Copyright (c) 2013-2025, Graham Dumpleton
Copyright (c) 2013-2019, Graham Dumpleton
All rights reserved.

Distributed under the following license(s):
Expand Down
2 changes: 1 addition & 1 deletion asv.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"repo": ".",
"environment_type": "virtualenv",
"install_timeout": 120,
"pythons": ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"],
"pythons": ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"],
"benchmark_dir": "tests/agent_benchmarks",
"env_dir": ".asv/env",
"results_dir": ".asv/results",
Expand Down
14 changes: 13 additions & 1 deletion newrelic/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,19 @@ def load_internal_plugins():


def load_external_plugins():
from importlib.metadata import entry_points
try:
# importlib.metadata was introduced into the standard library starting in Python 3.8.
from importlib.metadata import entry_points
except ImportError:
try:
# importlib_metadata is a backport library installable from PyPI.
from importlib_metadata import entry_points
except ImportError:
try:
# Fallback to pkg_resources, which is available in older versions of setuptools.
from pkg_resources import iter_entry_points as entry_points
except ImportError:
return

group = "newrelic.admin"

Expand Down
4 changes: 3 additions & 1 deletion newrelic/common/agent_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,9 @@ def _connection(self):
return self._connection_attr

retries = urllib3.Retry(total=False, connect=None, read=None, redirect=0, status=None)
self._connection_attr = self.CONNECTION_CLS(self._host, self._port, retries=retries, **self._connection_kwargs)
self._connection_attr = self.CONNECTION_CLS(
self._host, self._port, strict=True, retries=retries, **self._connection_kwargs
)
return self._connection_attr

def close_connection(self):
Expand Down
11 changes: 3 additions & 8 deletions newrelic/common/object_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,11 @@

import inspect

from newrelic.packages.wrapt import ( # noqa: F401
BaseObjectProxy,
apply_patch,
resolve_path,
wrap_object,
wrap_object_attribute,
)
from newrelic.packages.wrapt import BoundFunctionWrapper as _BoundFunctionWrapper
from newrelic.packages.wrapt import CallableObjectProxy as _CallableObjectProxy
from newrelic.packages.wrapt import FunctionWrapper as _FunctionWrapper
from newrelic.packages.wrapt import ObjectProxy as _ObjectProxy
from newrelic.packages.wrapt import apply_patch, resolve_path, wrap_object, wrap_object_attribute # noqa: F401

# We previously had our own pure Python implementation of the generic
# object wrapper but we now defer to using the wrapt module as its C
Expand All @@ -49,7 +44,7 @@
# ObjectProxy or FunctionWrapper should be used going forward.


class ObjectProxy(BaseObjectProxy):
class ObjectProxy(_ObjectProxy):
"""
This class provides method overrides for all object wrappers used by the
agent. These methods allow attributes to be defined with the special prefix
Expand Down
49 changes: 34 additions & 15 deletions newrelic/common/package_version_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import importlib.metadata as importlib_metadata
import sys
import warnings
from functools import lru_cache
Expand Down Expand Up @@ -96,17 +95,37 @@ def _get_package_version(name):
except Exception:
pass

try:
# In Python 3.10+ packages_distribution can be checked for as well.
if hasattr(importlib_metadata, "packages_distributions"):
distributions = importlib_metadata.packages_distributions()
distribution_name = distributions.get(name, name)
distribution_name = distribution_name[0] if isinstance(distribution_name, list) else distribution_name
else:
distribution_name = name

version = importlib_metadata.version(distribution_name)
if version not in NULL_VERSIONS:
return version
except Exception:
pass
importlib_metadata = None
# importlib.metadata was introduced into the standard library starting in Python 3.8.
importlib_metadata = getattr(sys.modules.get("importlib", None), "metadata", None)
if importlib_metadata is None:
# importlib_metadata is a backport library installable from PyPI.
try:
import importlib_metadata
except ImportError:
pass

if importlib_metadata is not None:
try:
# In Python 3.10+ packages_distribution can be checked for as well.
if hasattr(importlib_metadata, "packages_distributions"):
distributions = importlib_metadata.packages_distributions()
distribution_name = distributions.get(name, name)
distribution_name = distribution_name[0] if isinstance(distribution_name, list) else distribution_name
else:
distribution_name = name

version = importlib_metadata.version(distribution_name)
if version not in NULL_VERSIONS:
return version
except Exception:
pass

# Fallback to pkg_resources, which is available in older versions of setuptools.
if "pkg_resources" in sys.modules:
try:
version = sys.modules["pkg_resources"].get_distribution(name).version
if version not in NULL_VERSIONS:
return version
except Exception:
pass
28 changes: 26 additions & 2 deletions newrelic/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4226,7 +4226,19 @@ def _process_module_builtin_defaults():


def _process_module_entry_points():
from importlib.metadata import entry_points
try:
# importlib.metadata was introduced into the standard library starting in Python 3.8.
from importlib.metadata import entry_points
except ImportError:
try:
# importlib_metadata is a backport library installable from PyPI.
from importlib_metadata import entry_points
except ImportError:
try:
# Fallback to pkg_resources, which is available in older versions of setuptools.
from pkg_resources import iter_entry_points as entry_points
except ImportError:
return

group = "newrelic.hooks"

Expand Down Expand Up @@ -4294,7 +4306,19 @@ def _setup_instrumentation():


def _setup_extensions():
from importlib.metadata import entry_points
try:
# importlib.metadata was introduced into the standard library starting in Python 3.8.
from importlib.metadata import entry_points
except ImportError:
try:
# importlib_metadata is a backport library installable from PyPI.
from importlib_metadata import entry_points
except ImportError:
try:
# Fallback to pkg_resources, which is available in older versions of setuptools.
from pkg_resources import iter_entry_points as entry_points
except ImportError:
return

group = "newrelic.extension"

Expand Down
13 changes: 6 additions & 7 deletions newrelic/core/_thread_utilization.c
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,13 @@ static PyObject *NRUtilization_new(PyTypeObject *type,
return NULL;

/*
* Using a mutex to ensure this is compatible with free threaded Python interpreters.
* In the past, this relied on the GIL for thread safety with weakrefs but that was
* not reliable enough anyway.
* XXX Using a mutex for now just in case the calls to get
* the current thread are causing release of GIL in a
* multithreaded context. May explain why having issues with
* object referred to by weakrefs being corrupted. The GIL
* should technically be enough to protect us here.
*/

self->thread_mutex = PyThread_allocate_lock();

self->set_of_all_threads = PyDict_New();
Expand Down Expand Up @@ -452,10 +455,6 @@ moduleinit(void)
PyModule_AddObject(module, "ThreadUtilization",
(PyObject *)&NRUtilization_Type);

#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED);
#endif

return module;
}

Expand Down
3 changes: 2 additions & 1 deletion newrelic/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1119,7 +1119,8 @@ def _flatten(settings, o, name=None):
for key, value in vars(o).items():
# Remove any leading underscores on keys accessed through
# properties for reporting.
key = key.removeprefix("_")
if key.startswith("_"):
key = key[1:]

if name:
key = f"{name}.{key}"
Expand Down
8 changes: 7 additions & 1 deletion newrelic/core/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,13 @@ def _get_stdlib_builtin_module_names():
# Since sys.stdlib_module_names is not available in versions of python below 3.10,
# use isort's hardcoded stdlibs instead.
python_version = sys.version_info[0:2]
if python_version < (3, 10):
if python_version < (3,):
stdlibs = isort_stdlibs.py27.stdlib
elif (3, 7) <= python_version < (3, 8):
stdlibs = isort_stdlibs.py37.stdlib
elif python_version < (3, 9):
stdlibs = isort_stdlibs.py38.stdlib
elif python_version < (3, 10):
stdlibs = isort_stdlibs.py39.stdlib
elif python_version >= (3, 10):
stdlibs = sys.stdlib_module_names
Expand Down
3 changes: 0 additions & 3 deletions newrelic/hooks/database_dbapi2.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,6 @@ def callproc(self, procname, parameters=DEFAULT):
else:
return self.__wrapped__.callproc(procname)

def __iter__(self):
return iter(self.__wrapped__)


class ConnectionWrapper(ObjectProxy):
__cursor_wrapper__ = CursorWrapper
Expand Down
2 changes: 1 addition & 1 deletion newrelic/hooks/logger_structlog.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from newrelic.hooks.logger_logging import add_nr_linking_metadata


@functools.cache
@functools.lru_cache(maxsize=None)
def normalize_level_name(method_name):
# Look up level number for method name, using result to look up level name for that level number.
# Convert result to upper case, and default to UNKNOWN in case of errors or missing values.
Expand Down
14 changes: 2 additions & 12 deletions newrelic/packages/asgiref_compatibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,6 @@
import inspect


# Python 3.12 deprecates asyncio.iscoroutinefunction() as an alias for
# inspect.iscoroutinefunction(), whilst also removing the _is_coroutine marker.
# The latter is replaced with the inspect.markcoroutinefunction decorator.
# Until 3.12 is the minimum supported Python version, provide a shim.

if hasattr(inspect, "iscoroutinefunction"):
iscoroutinefunction = inspect.iscoroutinefunction
else:
iscoroutinefunction = asyncio.iscoroutinefunction # type: ignore[assignment]

def is_double_callable(application):
"""
Tests to see if an application is a legacy-style (double-callable) application.
Expand All @@ -56,10 +46,10 @@ def is_double_callable(application):
if hasattr(application, "__call__"):
# We only check to see if its __call__ is a coroutine function -
# if it's not, it still might be a coroutine function itself.
if iscoroutinefunction(application.__call__):
if asyncio.iscoroutinefunction(application.__call__):
return False
# Non-classes we just check directly
return not iscoroutinefunction(application)
return not asyncio.iscoroutinefunction(application)


def double_to_single_callable(application):
Expand Down
6 changes: 3 additions & 3 deletions newrelic/packages/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
# This file is used by dependabot to keep track of and recommend updates
# to the New Relic Python Agent's dependencies in newrelic/packages/.
opentelemetry_proto==1.32.1
urllib3==2.6.3
wrapt==2.0.1
asgiref==3.11.0 # We only vendor asgiref.compatibility.py
urllib3==1.26.19
wrapt==1.16.0
asgiref==3.6.0 # We only vendor asgiref.compatibility.py
2 changes: 1 addition & 1 deletion newrelic/packages/urllib3/LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2008-2020 Andrey Petrov and contributors.
Copyright (c) 2008-2020 Andrey Petrov and contributors (see CONTRIBUTORS.txt)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
Loading
Loading