From 42052494c0ef4a155eba8c424ff2fed340e41689 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 15 Jan 2026 12:10:24 +0100 Subject: [PATCH 1/5] tests: use importlib instead of pkgutil.get_loader pkgutil.get_loader has been removed in 3.14 --- tests/utils/stacks/tests.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/utils/stacks/tests.py b/tests/utils/stacks/tests.py index c4be88ff2..6f4d4094a 100644 --- a/tests/utils/stacks/tests.py +++ b/tests/utils/stacks/tests.py @@ -32,8 +32,8 @@ from __future__ import absolute_import +import importlib import os -import pkgutil import pytest from mock import Mock @@ -240,7 +240,8 @@ def test_get_lines_from_file(lineno, context, expected): def test_get_lines_from_loader(lineno, context, expected): stacks.get_lines_from_file.cache_clear() module = "tests.utils.stacks.linenos" - loader = pkgutil.get_loader(module) + spec = importlib.util.find_spec(module) + loader = spec.loader if spec is not None else None fname = os.path.join(os.path.dirname(__file__), "linenos.py") result = stacks.get_lines_from_file(fname, lineno, context, loader=loader, module_name=module) assert result == expected From 12802a47a94f7d11f1d9c7ee1b8141116bcfdcb4 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 15 Jan 2026 12:11:44 +0100 Subject: [PATCH 2/5] tests: use plain open instead of codecs.open Since it has been deprecated --- setup.py | 3 +-- tests/fixtures.py | 7 +++---- tests/utils/test_wildcard_matcher_cases/conftest.py | 3 +-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/setup.py b/setup.py index b41dcaca9..ffb038307 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,6 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import ast -import codecs import os from setuptools import setup @@ -68,7 +67,7 @@ def get_version(): :return: a string, indicating the version """ - version_file = codecs.open(os.path.join("elasticapm", "version.py"), encoding="utf-8") + version_file = open(os.path.join("elasticapm", "version.py"), encoding="utf-8") for line in version_file: if line.startswith("__version__"): version_tuple = ast.literal_eval(line.split(" = ")[1]) diff --git a/tests/fixtures.py b/tests/fixtures.py index a559791bf..25d21ee5d 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -28,7 +28,6 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import codecs import gzip import io import itertools @@ -75,11 +74,11 @@ SPAN_TYPES = json.load(f) -with codecs.open(ERRORS_SCHEMA, encoding="utf8") as errors_json, codecs.open( +with open(ERRORS_SCHEMA, encoding="utf8") as errors_json, open( TRANSACTIONS_SCHEMA, encoding="utf8" -) as transactions_json, codecs.open(SPAN_SCHEMA, encoding="utf8") as span_json, codecs.open( +) as transactions_json, open(SPAN_SCHEMA, encoding="utf8") as span_json, open( METRICSET_SCHEMA, encoding="utf8" -) as metricset_json, codecs.open( +) as metricset_json, open( METADATA_SCHEMA, encoding="utf8" ) as metadata_json: VALIDATORS = { diff --git a/tests/utils/test_wildcard_matcher_cases/conftest.py b/tests/utils/test_wildcard_matcher_cases/conftest.py index c672ff6f4..9d075dd24 100644 --- a/tests/utils/test_wildcard_matcher_cases/conftest.py +++ b/tests/utils/test_wildcard_matcher_cases/conftest.py @@ -27,7 +27,6 @@ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import codecs import json import os @@ -40,7 +39,7 @@ def pytest_generate_tests(metafunc): json_cases = os.path.join( os.path.dirname(__file__), "..", "..", "upstream", "json-specs", "wildcard_matcher_tests.json" ) - with codecs.open(json_cases, encoding="utf8") as test_cases_file: + with open(json_cases, encoding="utf8") as test_cases_file: test_cases = json.load(test_cases_file) for test_case, pattern_sets in test_cases.items(): for pattern, texts in pattern_sets.items(): From b0a2047aa93c79b97c727729b31c659bd76f371c Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 15 Jan 2026 12:30:35 +0100 Subject: [PATCH 3/5] elasticapm/contrib/django: swallow exceptions explicitly In 3.14 after https://peps.python.org/pep-0765/ the pattern of using return in a finally block is considered harmful. I guess the intent here is to just swallow any exception raised in the try block and always return the response. --- elasticapm/contrib/django/middleware/__init__.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/elasticapm/contrib/django/middleware/__init__.py b/elasticapm/contrib/django/middleware/__init__.py index c4637d087..612bb5c96 100644 --- a/elasticapm/contrib/django/middleware/__init__.py +++ b/elasticapm/contrib/django/middleware/__init__.py @@ -110,8 +110,9 @@ def process_request_wrapper(wrapped, instance, args, kwargs): elasticapm.set_transaction_name( build_name_with_http_method_prefix(get_name_from_middleware(wrapped, instance), request) ) - finally: - return response + except Exception: + pass + return response def process_response_wrapper(wrapped, instance, args, kwargs): @@ -125,8 +126,9 @@ def process_response_wrapper(wrapped, instance, args, kwargs): elasticapm.set_transaction_name( build_name_with_http_method_prefix(get_name_from_middleware(wrapped, instance), request) ) - finally: - return response + except Exception: + pass + return response class TracingMiddleware(MiddlewareMixin, ElasticAPMClientMiddlewareMixin): From c7926f53b06ba4fe09f3ed95e1ed111d21c733ad Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 15 Jan 2026 14:25:53 +0100 Subject: [PATCH 4/5] tests: drop unused import --- tests/contrib/asyncio/starlette_tests.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/contrib/asyncio/starlette_tests.py b/tests/contrib/asyncio/starlette_tests.py index 38c51fa08..055cf01ad 100644 --- a/tests/contrib/asyncio/starlette_tests.py +++ b/tests/contrib/asyncio/starlette_tests.py @@ -28,8 +28,6 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from shutil import ExecError - from tests.fixtures import TempStoreClient import pytest # isort:skip From 0377a4859b52caf4e721c75a42218c4ab426c72f Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 15 Jan 2026 14:34:23 +0100 Subject: [PATCH 5/5] Run tests against 3.14 in full matrix --- .ci/.matrix_exclude.yml | 78 ++++++++++++++++++++++++++++++++++++- .ci/.matrix_python_full.yml | 1 + Makefile | 2 +- 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/.ci/.matrix_exclude.yml b/.ci/.matrix_exclude.yml index 93d0fa4df..c7089061f 100644 --- a/.ci/.matrix_exclude.yml +++ b/.ci/.matrix_exclude.yml @@ -57,6 +57,8 @@ exclude: FRAMEWORK: flask-2.3 - VERSION: python-3.7 FRAMEWORK: flask-3.0 + - VERSION: python-3.14 + FRAMEWORK: flask-1.0 # Python 3.10 removed a bunch of classes from collections, now in collections.abc - VERSION: python-3.10 FRAMEWORK: django-1.11 @@ -80,6 +82,12 @@ exclude: FRAMEWORK: celery-5-django-3 - VERSION: python-3.13 # https://github.com/celery/billiard/issues/377 FRAMEWORK: celery-5-django-4 + - VERSION: python-3.14 # https://github.com/celery/billiard/issues/377 + FRAMEWORK: celery-5-flask-2 + - VERSION: python-3.14 # https://github.com/celery/billiard/issues/377 + FRAMEWORK: celery-5-django-3 + - VERSION: python-3.14 # https://github.com/celery/billiard/issues/377 + FRAMEWORK: celery-5-django-4 - VERSION: python-3.10 FRAMEWORK: graphene-2 - VERSION: python-3.10 @@ -146,7 +154,35 @@ exclude: FRAMEWORK: aiohttp-4.0 - VERSION: python-3.13 FRAMEWORK: cassandra-3.4 - - VERSION: python-3.13 + - VERSION: python-3.14 + FRAMEWORK: django-1.11 + - VERSION: python-3.14 + FRAMEWORK: django-2.0 + - VERSION: python-3.14 + FRAMEWORK: django-2.1 + - VERSION: python-3.14 + FRAMEWORK: django-2.2 + - VERSION: python-3.14 + FRAMEWORK: django-3.0 + - VERSION: python-3.14 + FRAMEWORK: django-3.1 + - VERSION: python-3.14 + FRAMEWORK: django-3.2 + - VERSION: python-3.14 + FRAMEWORK: django-4.0 + - VERSION: python-3.14 + FRAMEWORK: django-4.2 + - VERSION: python-3.14 + FRAMEWORK: django-5.0 + - VERSION: python-3.14 + FRAMEWORK: graphene-2 + - VERSION: python-3.14 + FRAMEWORK: aiohttp-3.0 + - VERSION: python-3.14 + FRAMEWORK: aiohttp-4.0 + - VERSION: python-3.14 + FRAMEWORK: cassandra-3.4 + - VERSION: python-3.14 FRAMEWORK: pymongo-3.5 # pymongo - VERSION: python-3.10 @@ -157,6 +193,8 @@ exclude: FRAMEWORK: pymongo-3.1 - VERSION: python-3.13 FRAMEWORK: pymongo-3.1 + - VERSION: python-3.14 + FRAMEWORK: pymongo-3.1 - VERSION: python-3.10 FRAMEWORK: pymongo-3.2 - VERSION: python-3.11 @@ -165,6 +203,8 @@ exclude: FRAMEWORK: pymongo-3.2 - VERSION: python-3.13 FRAMEWORK: pymongo-3.2 + - VERSION: python-3.14 + FRAMEWORK: pymongo-3.2 - VERSION: python-3.10 FRAMEWORK: pymongo-3.3 - VERSION: python-3.11 @@ -173,6 +213,8 @@ exclude: FRAMEWORK: pymongo-3.3 - VERSION: python-3.13 FRAMEWORK: pymongo-3.3 + - VERSION: python-3.14 + FRAMEWORK: pymongo-3.3 - VERSION: python-3.8 FRAMEWORK: pymongo-3.4 - VERSION: python-3.9 @@ -185,6 +227,12 @@ exclude: FRAMEWORK: pymongo-3.4 - VERSION: python-3.13 FRAMEWORK: pymongo-3.4 + - VERSION: python-3.13 + FRAMEWORK: pymongo-3.5 + - VERSION: python-3.14 + FRAMEWORK: pymongo-3.4 + - VERSION: python-3.14 + FRAMEWORK: pymongo-3.5 - VERSION: pypy-3 FRAMEWORK: pymongo-3.0 # pymssql @@ -212,6 +260,10 @@ exclude: FRAMEWORK: boto3-1.5 - VERSION: python-3.13 FRAMEWORK: boto3-1.6 + - VERSION: python-3.14 + FRAMEWORK: boto3-1.5 + - VERSION: python-3.14 + FRAMEWORK: boto3-1.6 # aiohttp client, only supported in Python 3.7+ - VERSION: pypy-3 FRAMEWORK: aiohttp-3.0 @@ -259,6 +311,8 @@ exclude: FRAMEWORK: asyncpg-0.28 - VERSION: python-3.13 FRAMEWORK: asyncpg-0.28 + - VERSION: python-3.14 + FRAMEWORK: asyncpg-0.28 # sanic - VERSION: pypy-3 FRAMEWORK: sanic-newest @@ -272,6 +326,8 @@ exclude: FRAMEWORK: sanic-newest - VERSION: python-3.13 FRAMEWORK: sanic-20.12 + - VERSION: python-3.14 + FRAMEWORK: sanic-20.12 # aioredis - VERSION: pypy-3 FRAMEWORK: aioredis-newest @@ -315,6 +371,14 @@ exclude: FRAMEWORK: twisted-16 - VERSION: python-3.13 FRAMEWORK: twisted-15 + - VERSION: python-3.14 + FRAMEWORK: twisted-18 + - VERSION: python-3.14 + FRAMEWORK: twisted-17 + - VERSION: python-3.14 + FRAMEWORK: twisted-16 + - VERSION: python-3.14 + FRAMEWORK: twisted-15 # pylibmc - VERSION: python-3.11 FRAMEWORK: pylibmc-1.4 @@ -322,6 +386,8 @@ exclude: FRAMEWORK: pylibmc-1.4 - VERSION: python-3.13 FRAMEWORK: pylibmc-1.4 + - VERSION: python-3.14 + FRAMEWORK: pylibmc-1.4 # grpc - VERSION: python-3.6 FRAMEWORK: grpc-newest @@ -339,6 +405,8 @@ exclude: FRAMEWORK: grpc-1.24 - VERSION: python-3.13 FRAMEWORK: grpc-1.24 + - VERSION: python-3.14 + FRAMEWORK: grpc-1.24 - VERSION: python-3.7 FRAMEWORK: flask-1.0 - VERSION: python-3.7 @@ -350,6 +418,8 @@ exclude: FRAMEWORK: sanic-20.12 # no wheels available yet - VERSION: python-3.13 FRAMEWORK: cassandra-newest # c extension issue + - VERSION: python-3.14 + FRAMEWORK: cassandra-newest # c extension issue # httpx - VERSION: python-3.13 FRAMEWORK: httpx-0.13 @@ -357,3 +427,9 @@ exclude: FRAMEWORK: httpx-0.14 - VERSION: python-3.13 FRAMEWORK: httpx-0.21 + - VERSION: python-3.14 + FRAMEWORK: httpx-0.13 + - VERSION: python-3.14 + FRAMEWORK: httpx-0.14 + - VERSION: python-3.14 + FRAMEWORK: httpx-0.21 diff --git a/.ci/.matrix_python_full.yml b/.ci/.matrix_python_full.yml index bb763b7ca..56b272fbc 100644 --- a/.ci/.matrix_python_full.yml +++ b/.ci/.matrix_python_full.yml @@ -7,4 +7,5 @@ VERSION: - python-3.11 - python-3.12 - python-3.13 + - python-3.14 # - pypy-3 # excluded due to build issues with SQLite/Django diff --git a/Makefile b/Makefile index b2d00f400..90dcd4744 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ test: # delete any __pycache__ folders to avoid hard-to-debug caching issues find . -type f -name '*.py[co]' -delete -o -type d -name __pycache__ -delete # pypy3 should be added to the first `if` once it supports py3.7 - if [[ "$$PYTHON_VERSION" =~ ^(3.7|3.8|3.9|3.10|3.11|3.12|3.13|nightly)$$ ]] ; then \ + if [[ "$$PYTHON_VERSION" =~ ^(3.7|3.8|3.9|3.10|3.11|3.12|3.13|3.14|nightly)$$ ]] ; then \ echo "Python 3.7+, with asyncio"; \ pytest -v $(PYTEST_ARGS) --showlocals $(PYTEST_MARKER) $(PYTEST_JUNIT); \ else \