Skip to content
Open
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
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 cp3.8
RUN uv python install -f cp3.14 cp3.14t cp3.13 cp3.12 cp3.11 cp3.10 cp3.9

# 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 @@ -31,7 +31,7 @@ jobs:
timeout-minutes: 30
strategy:
matrix:
python: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
python: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]

env:
ASV_FACTOR: "1.1"
Expand Down
4 changes: 0 additions & 4 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ jobs:
matrix:
include:
# Linux glibc
- wheel: cp38-manylinux
os: ubuntu-24.04
- wheel: cp39-manylinux
os: ubuntu-24.04
- wheel: cp310-manylinux
Expand All @@ -48,8 +46,6 @@ jobs:
- 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 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.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"],
"pythons": ["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: 1 addition & 13 deletions newrelic/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,19 +120,7 @@ def load_internal_plugins():


def load_external_plugins():
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
from importlib.metadata import entry_points

group = "newrelic.admin"

Expand Down
49 changes: 15 additions & 34 deletions newrelic/common/package_version_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# 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 @@ -95,37 +96,17 @@ def _get_package_version(name):
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
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
28 changes: 2 additions & 26 deletions newrelic/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4217,19 +4217,7 @@ def _process_module_builtin_defaults():


def _process_module_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
from importlib.metadata import entry_points

group = "newrelic.hooks"

Expand Down Expand Up @@ -4297,19 +4285,7 @@ def _setup_instrumentation():


def _setup_extensions():
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
from importlib.metadata import entry_points

group = "newrelic.extension"

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

if name:
key = f"{name}.{key}"
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.lru_cache(maxsize=None)
@functools.cache
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
4 changes: 1 addition & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,9 @@ readme = "README.md"
# "LICENSE",
# "THIRD_PARTY_NOTICES.md",
# ]
requires-python = ">=3.8" # python_requires is also located in setup.py
requires-python = ">=3.9" # python_requires is also located in setup.py
classifiers = [
"Development Status :: 5 - Production/Stable",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
Expand Down Expand Up @@ -105,7 +104,6 @@ git_describe_command = 'git describe --dirty --tags --long --match "*.*.*"'
[tool.ruff]
output-format = "grouped"
line-length = 120
target-version = "py38"
force-exclude = true # Fixes issue with megalinter config preventing exclusion of files
extend-exclude = [
"newrelic/packages/",
Expand Down
9 changes: 4 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@

python_version = sys.version_info[:2]

if python_version >= (3, 8):
pass
else:
if python_version < (3, 9):
error_msg = (
"The New Relic Python agent only supports Python 3.8+. We recommend upgrading to a newer version of Python."
"The New Relic Python agent only supports Python 3.9+. We recommend upgrading to a newer version of Python."
)

try:
Expand All @@ -34,6 +32,7 @@
(3, 5): "5.24.0.153",
(3, 6): "7.16.0.178",
(3, 7): "10.17.0",
(3, 8): "11.2.0",
}
last_supported_version = last_supported_version_lookup.get(python_version, None)

Expand Down Expand Up @@ -128,7 +127,7 @@ def build_extension(self, ext):

kwargs.update(
{
"python_requires": ">=3.8", # python_requires is also located in pyproject.toml
"python_requires": ">=3.9", # python_requires is also located in pyproject.toml
"zip_safe": False,
"packages": packages,
"package_data": {
Expand Down
27 changes: 2 additions & 25 deletions tests/agent_unittests/test_package_version_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,12 @@
)

# Notes:
# importlib.metadata was a provisional addition to the std library in PY38 and PY39
# importlib.metadata was a provisional addition to the std library in Python 3.8 and 3.9
# while pkg_resources was deprecated.
# importlib.metadata is no longer provisional in PY310+. It added some attributes
# importlib.metadata is no longer provisional in Python 3.10+. It added some attributes
# such as distribution_packages and removed pkg_resources.

IS_PY38_PLUS = sys.version_info[:2] >= (3, 8)
IS_PY310_PLUS = sys.version_info[:2] >= (3, 10)
SKIP_IF_NOT_IMPORTLIB_METADATA = pytest.mark.skipif(not IS_PY38_PLUS, reason="importlib.metadata is not supported.")
SKIP_IF_IMPORTLIB_METADATA = pytest.mark.skipif(
IS_PY38_PLUS, reason="importlib.metadata is preferred over pkg_resources."
)
SKIP_IF_NOT_PY310_PLUS = pytest.mark.skipif(not IS_PY310_PLUS, reason="These features were added in 3.10+")


Expand Down Expand Up @@ -101,38 +96,20 @@ def test_get_package_version_tuple(monkeypatch, attr, value, expected_value):
assert version == expected_value


@SKIP_IF_NOT_IMPORTLIB_METADATA
@validate_function_called("importlib.metadata", "version")
def test_importlib_dot_metadata():
# Test for importlib.metadata from the standard library.
version = get_package_version("pytest")
assert version not in NULL_VERSIONS, version


@SKIP_IF_IMPORTLIB_METADATA
@validate_function_called("importlib_metadata", "version")
def test_importlib_underscore_metadata():
# Test for importlib_metadata, a backport library available on PyPI.
version = get_package_version("pytest")
assert version not in NULL_VERSIONS, version


@SKIP_IF_NOT_PY310_PLUS
@validate_function_called("importlib.metadata", "packages_distributions")
def test_mapping_import_to_distribution_packages():
version = get_package_version("pytest")
assert version not in NULL_VERSIONS, version


@SKIP_IF_IMPORTLIB_METADATA
@validate_function_called("pkg_resources", "get_distribution")
def test_pkg_resources_metadata(monkeypatch):
# Prevent importlib_metadata from being used by these tests
monkeypatch.setitem(sys.modules, "importlib_metadata", None)
version = get_package_version("pytest")
assert version not in NULL_VERSIONS, version


def _getattr_deprecation_warning(attr):
if attr == "__version__":
warnings.warn("Testing deprecation warnings.", DeprecationWarning, stacklevel=2)
Expand Down
1 change: 0 additions & 1 deletion tests/framework_starlette/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ def test_exception_in_middleware(target_application, app_name):
app = target_application[app_name]

# Starlette >=0.15 and <0.17 raises an exception group instead of reraising the ValueError
# This only occurs on Python versions >=3.8
if (0, 15, 0) <= starlette_version < (0, 17, 0):
from anyio._backends._asyncio import ExceptionGroup

Expand Down
4 changes: 1 addition & 3 deletions tests/framework_strawberry/_target_schema_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@

from __future__ import annotations

from typing import List

import strawberry

try:
Expand Down Expand Up @@ -68,7 +66,7 @@ async def resolve_search(contains: str):
class Query:
library: Library = field(resolver=resolve_library)
hello: str = field(resolver=resolve_hello)
search: List[Item] = field(resolver=resolve_search)
search: list[Item] = field(resolver=resolve_search)
echo: str = field(resolver=resolve_echo)
storage: Storage = field(resolver=resolve_storage)
error: str | None = field(resolver=resolve_error)
Expand Down
10 changes: 5 additions & 5 deletions tests/framework_strawberry/_target_schema_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from __future__ import annotations

from typing import List, Union
from typing import Union

import strawberry

Expand Down Expand Up @@ -56,12 +56,12 @@ class Magazine:
class Library:
id: int
branch: str
magazine: List[Magazine]
book: List[Book]
magazine: list[Magazine]
book: list[Book]


Item = Union[Book, Magazine]
Storage = List[str]
Storage = list[str]


authors = [
Expand Down Expand Up @@ -138,7 +138,7 @@ def resolve_search(contains: str):
class Query:
library: Library = field(resolver=resolve_library)
hello: str = field(resolver=resolve_hello)
search: List[Item] = field(resolver=resolve_search)
search: list[Item] = field(resolver=resolve_search)
echo: str = field(resolver=resolve_echo)
storage: Storage = field(resolver=resolve_storage)
error: str | None = field(resolver=resolve_error)
Expand Down
8 changes: 4 additions & 4 deletions tests/mlmodel_sklearn/test_inference_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ def _test():
_test()


label_type = "bool" if sys.version_info < (3, 8) else "numeric"
true_label_value = "True" if sys.version_info < (3, 8) else "1.0"
false_label_value = "False" if sys.version_info < (3, 8) else "0.0"
label_type = "numeric"
true_label_value = "1.0"
false_label_value = "0.0"
pandas_df_bool_recorded_custom_events = [
(
{"type": "InferenceData"},
Expand All @@ -87,7 +87,7 @@ def test_pandas_df_bool_feature_event():
def _test():
import sklearn.tree

dtype_name = "bool" if sys.version_info < (3, 8) else "boolean"
dtype_name = "boolean"
x_train = pd.DataFrame({"col1": [True, False], "col2": [True, False]}, dtype=dtype_name)
y_train = pd.DataFrame({"label": [True, False]}, dtype=dtype_name)
x_test = pd.DataFrame({"col1": [True], "col2": [True]}, dtype=dtype_name)
Expand Down
Loading