diff --git a/.github/.trivyignore b/.github/.trivyignore index a3242f56c5..1f9f11bd30 100644 --- a/.github/.trivyignore +++ b/.github/.trivyignore @@ -2,7 +2,7 @@ # Ignored Vulnerabilities # ======================= -# Accepting risk due to Python 3.7 and 3.8 support. +# Accepting risk due to Python 3.8 support. CVE-2025-50181 # Not relevant, only affects Pyodide diff --git a/.github/containers/Dockerfile b/.github/containers/Dockerfile index c9e22a93cb..97fc1bf9c4 100644 --- a/.github/containers/Dockerfile +++ b/.github/containers/Dockerfile @@ -111,7 +111,7 @@ RUN echo 'eval "$(pyenv init -)"' >>${HOME}/.bashrc && \ pyenv update # Install Python -ARG PYTHON_VERSIONS="3.12 3.11 3.10 3.9 3.8 3.7 3.13 pypy3.10-7.3.17" +ARG PYTHON_VERSIONS="3.13 3.12 3.11 3.10 3.9 3.8 pypy3.10-7.3.17" COPY --chown=0:0 --chmod=755 ./install-python.sh /tmp/install-python.sh RUN /tmp/install-python.sh && \ rm /tmp/install-python.sh diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 707b4fe3b0..0d6b7009f2 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -23,42 +23,6 @@ permissions: contents: read jobs: - build-linux-py3-legacy: - runs-on: ubuntu-24.04 - strategy: - fail-fast: false - matrix: - wheel: - - cp37-manylinux - - cp37-musllinux - - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 5.0.0 - with: - persist-credentials: false - fetch-depth: 0 - - - name: Setup QEMU - uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # 3.6.0 - - - name: Build Wheels - uses: pypa/cibuildwheel@8d945475ac4b1aac4ae08b2fd27db9917158b6ce # 2.17.0 - env: - CIBW_PLATFORM: linux - CIBW_BUILD: "${{ matrix.wheel }}*" - CIBW_ARCHS_LINUX: x86_64 aarch64 - CIBW_ENVIRONMENT: "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_TEST_REQUIRES: pytest - CIBW_TEST_COMMAND: "PYTHONPATH={project}/tests pytest {project}/tests/agent_unittests -vx" - - - name: Upload Artifacts - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # 4.6.2 - with: - name: ${{ github.job }}-${{ matrix.wheel }} - path: ./wheelhouse/*.whl - if-no-files-found: error - retention-days: 1 - build-linux-py3: runs-on: ubuntu-24.04 strategy: @@ -151,7 +115,6 @@ jobs: attestations: write needs: - - build-linux-py3-legacy - build-linux-py3 - build-sdist diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 7aa68f22dd..2aceaea9fa 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -35,15 +35,6 @@ Distributed under the following license(s): * [The Apache License, Version 2.0 License](https://opensource.org/license/apache-2-0/) -## [time.monotonic](newrelic/common/_monotonic.c) - -Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Python Software Foundation; All Rights Reserved - -Distributed under the following license(s): - -* [Python Software Foundation](https://docs.python.org/3/license.html) - - ## [urllib3](https://pypi.org/project/urllib3) Copyright (c) 2008-2019 Andrey Petrov and contributors (see CONTRIBUTORS.txt) diff --git a/newrelic/agent.py b/newrelic/agent.py index 43f452832c..af98f03c55 100644 --- a/newrelic/agent.py +++ b/newrelic/agent.py @@ -15,56 +15,9 @@ from newrelic.api.application import application_instance as __application from newrelic.api.application import application_settings as __application_settings from newrelic.api.application import register_application as __register_application -from newrelic.api.log import NewRelicContextFormatter as __NewRelicContextFormatter -from newrelic.api.time_trace import add_custom_span_attribute as __add_custom_span_attribute -from newrelic.api.time_trace import current_trace as __current_trace -from newrelic.api.time_trace import get_linking_metadata as __get_linking_metadata -from newrelic.api.time_trace import notice_error as __notice_error -from newrelic.api.transaction import accept_distributed_trace_headers as __accept_distributed_trace_headers -from newrelic.api.transaction import add_custom_attribute as __add_custom_attribute -from newrelic.api.transaction import add_custom_attributes as __add_custom_attributes -from newrelic.api.transaction import add_framework_info as __add_framework_info -from newrelic.api.transaction import capture_request_params as __capture_request_params -from newrelic.api.transaction import current_span_id as __current_span_id -from newrelic.api.transaction import current_trace_id as __current_trace_id -from newrelic.api.transaction import current_transaction as __current_transaction -from newrelic.api.transaction import disable_browser_autorum as __disable_browser_autorum -from newrelic.api.transaction import end_of_transaction as __end_of_transaction -from newrelic.api.transaction import get_browser_timing_header as __get_browser_timing_header -from newrelic.api.transaction import ignore_transaction as __ignore_transaction -from newrelic.api.transaction import insert_distributed_trace_headers as __insert_distributed_trace_headers -from newrelic.api.transaction import record_custom_event as __record_custom_event -from newrelic.api.transaction import record_custom_metric as __record_custom_metric -from newrelic.api.transaction import record_custom_metrics as __record_custom_metrics -from newrelic.api.transaction import record_log_event as __record_log_event -from newrelic.api.transaction import record_ml_event as __record_ml_event -from newrelic.api.transaction import set_background_task as __set_background_task -from newrelic.api.transaction import set_transaction_name as __set_transaction_name -from newrelic.api.transaction import suppress_apdex_metric as __suppress_apdex_metric -from newrelic.api.transaction import suppress_transaction_trace as __suppress_transaction_trace -from newrelic.api.wsgi_application import WSGIApplicationWrapper as __WSGIApplicationWrapper -from newrelic.api.wsgi_application import wrap_wsgi_application as __wrap_wsgi_application -from newrelic.api.wsgi_application import wsgi_application as __wsgi_application -from newrelic.config import extra_settings as __extra_settings -from newrelic.config import initialize as __initialize -from newrelic.core.agent import register_data_source as __register_data_source -from newrelic.core.agent import shutdown_agent as __shutdown_agent -from newrelic.core.config import global_settings as __global_settings -from newrelic.samplers.decorators import data_source_factory as __data_source_factory -from newrelic.samplers.decorators import data_source_generator as __data_source_generator - -try: - from newrelic.api.asgi_application import ASGIApplicationWrapper as __ASGIApplicationWrapper - from newrelic.api.asgi_application import asgi_application as __asgi_application - from newrelic.api.asgi_application import wrap_asgi_application as __wrap_asgi_application -except SyntaxError: - - def __asgi_application(*args, **kwargs): - pass - - __ASGIApplicationWrapper = __asgi_application - __wrap_asgi_application = __asgi_application - +from newrelic.api.asgi_application import ASGIApplicationWrapper as __ASGIApplicationWrapper +from newrelic.api.asgi_application import asgi_application as __asgi_application +from newrelic.api.asgi_application import wrap_asgi_application as __wrap_asgi_application from newrelic.api.background_task import BackgroundTask as __BackgroundTask from newrelic.api.background_task import BackgroundTaskWrapper as __BackgroundTaskWrapper from newrelic.api.background_task import background_task as __background_task @@ -96,6 +49,7 @@ def __asgi_application(*args, **kwargs): from newrelic.api.html_insertion import insert_html_snippet as __insert_html_snippet from newrelic.api.html_insertion import verify_body_exists as __verify_body_exists from newrelic.api.llm_custom_attributes import WithLlmCustomAttributes as __WithLlmCustomAttributes +from newrelic.api.log import NewRelicContextFormatter as __NewRelicContextFormatter from newrelic.api.message_trace import MessageTrace as __MessageTrace from newrelic.api.message_trace import MessageTraceWrapper as __MessageTraceWrapper from newrelic.api.message_trace import message_trace as __message_trace @@ -112,7 +66,33 @@ def __asgi_application(*args, **kwargs): from newrelic.api.profile_trace import wrap_profile_trace as __wrap_profile_trace from newrelic.api.settings import set_error_group_callback as __set_error_group_callback from newrelic.api.supportability import wrap_api_call as __wrap_api_call +from newrelic.api.time_trace import add_custom_span_attribute as __add_custom_span_attribute +from newrelic.api.time_trace import current_trace as __current_trace +from newrelic.api.time_trace import get_linking_metadata as __get_linking_metadata +from newrelic.api.time_trace import notice_error as __notice_error +from newrelic.api.transaction import accept_distributed_trace_headers as __accept_distributed_trace_headers +from newrelic.api.transaction import add_custom_attribute as __add_custom_attribute +from newrelic.api.transaction import add_custom_attributes as __add_custom_attributes +from newrelic.api.transaction import add_framework_info as __add_framework_info +from newrelic.api.transaction import capture_request_params as __capture_request_params +from newrelic.api.transaction import current_span_id as __current_span_id +from newrelic.api.transaction import current_trace_id as __current_trace_id +from newrelic.api.transaction import current_transaction as __current_transaction +from newrelic.api.transaction import disable_browser_autorum as __disable_browser_autorum +from newrelic.api.transaction import end_of_transaction as __end_of_transaction +from newrelic.api.transaction import get_browser_timing_header as __get_browser_timing_header +from newrelic.api.transaction import ignore_transaction as __ignore_transaction +from newrelic.api.transaction import insert_distributed_trace_headers as __insert_distributed_trace_headers +from newrelic.api.transaction import record_custom_event as __record_custom_event +from newrelic.api.transaction import record_custom_metric as __record_custom_metric +from newrelic.api.transaction import record_custom_metrics as __record_custom_metrics +from newrelic.api.transaction import record_log_event as __record_log_event +from newrelic.api.transaction import record_ml_event as __record_ml_event +from newrelic.api.transaction import set_background_task as __set_background_task +from newrelic.api.transaction import set_transaction_name as __set_transaction_name from newrelic.api.transaction import set_user_id as __set_user_id +from newrelic.api.transaction import suppress_apdex_metric as __suppress_apdex_metric +from newrelic.api.transaction import suppress_transaction_trace as __suppress_transaction_trace from newrelic.api.transaction_name import TransactionNameWrapper as __TransactionNameWrapper from newrelic.api.transaction_name import transaction_name as __transaction_name from newrelic.api.transaction_name import wrap_transaction_name as __wrap_transaction_name @@ -120,6 +100,9 @@ def __asgi_application(*args, **kwargs): from newrelic.api.web_transaction import WebTransactionWrapper as __WebTransactionWrapper from newrelic.api.web_transaction import web_transaction as __web_transaction from newrelic.api.web_transaction import wrap_web_transaction as __wrap_web_transaction +from newrelic.api.wsgi_application import WSGIApplicationWrapper as __WSGIApplicationWrapper +from newrelic.api.wsgi_application import wrap_wsgi_application as __wrap_wsgi_application +from newrelic.api.wsgi_application import wsgi_application as __wsgi_application from newrelic.common.object_names import callable_name as __callable_name from newrelic.common.object_wrapper import CallableObjectProxy as __CallableObjectProxy from newrelic.common.object_wrapper import FunctionWrapper as __FunctionWrapper @@ -143,6 +126,13 @@ def __asgi_application(*args, **kwargs): from newrelic.common.object_wrapper import wrap_out_function as __wrap_out_function from newrelic.common.object_wrapper import wrap_post_function as __wrap_post_function from newrelic.common.object_wrapper import wrap_pre_function as __wrap_pre_function +from newrelic.config import extra_settings as __extra_settings +from newrelic.config import initialize as __initialize +from newrelic.core.agent import register_data_source as __register_data_source +from newrelic.core.agent import shutdown_agent as __shutdown_agent +from newrelic.core.config import global_settings as __global_settings +from newrelic.samplers.decorators import data_source_factory as __data_source_factory +from newrelic.samplers.decorators import data_source_generator as __data_source_generator # EXPERIMENTAL - Generator traces are currently experimental and may not # exist in this form in future versions of the agent. diff --git a/newrelic/common/_monotonic.c b/newrelic/common/_monotonic.c deleted file mode 100644 index 95f28f1a05..0000000000 --- a/newrelic/common/_monotonic.c +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2010 New Relic, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - */ - -/* - * This file is a modified back port of the monotonic() function from - * Python 3.3. The original code was released under the Python Software - * Foundation License Version 2. - */ - -#include - -#include - -#if defined(__APPLE__) -#include -#include -#endif - -#ifndef PyVarObject_HEAD_INIT -#define PyVarObject_HEAD_INIT(type, size) PyObject_HEAD_INIT(type) size, -#endif - -/* ------------------------------------------------------------------------- */ - -static PyObject *monotonic(PyObject *self, PyObject *args) -{ -#if defined(MS_WINDOWS) - static ULONGLONG (*GetTickCount64) (void) = NULL; - static ULONGLONG (CALLBACK *Py_GetTickCount64)(void); - static int has_getickcount64 = -1; - double result; - - if (has_getickcount64 == -1) { - /* GetTickCount64() was added to Windows Vista */ - if (winver.dwMajorVersion >= 6) { - HINSTANCE hKernel32; - hKernel32 = GetModuleHandleW(L"KERNEL32"); - *(FARPROC*)&Py_GetTickCount64 = GetProcAddress(hKernel32, - "GetTickCount64"); - has_getickcount64 = (Py_GetTickCount64 != NULL); - } - else - has_getickcount64 = 0; - } - - if (has_getickcount64) { - ULONGLONG ticks; - ticks = Py_GetTickCount64(); - result = (double)ticks * 1e-3; - } - else { - static DWORD last_ticks = 0; - static DWORD n_overflow = 0; - DWORD ticks; - - ticks = GetTickCount(); - if (ticks < last_ticks) - n_overflow++; - last_ticks = ticks; - - result = ldexp(n_overflow, 32); - result += ticks; - result *= 1e-3; - } - - return PyFloat_FromDouble(result); - -#elif defined(__APPLE__) - static mach_timebase_info_data_t timebase; - uint64_t time; - double secs; - - if (timebase.denom == 0) { - /* According to the Technical Q&A QA1398, mach_timebase_info() cannot - fail: https://developer.apple.com/library/mac/#qa/qa1398/ */ - (void)mach_timebase_info(&timebase); - } - - time = mach_absolute_time(); - secs = (double)time * timebase.numer / timebase.denom * 1e-9; - - return PyFloat_FromDouble(secs); - -#elif (defined(CLOCK_HIGHRES) || defined(CLOCK_MONOTONIC)) - struct timespec tp; -#ifdef CLOCK_HIGHRES - const clockid_t clk_id = CLOCK_HIGHRES; -#else - const clockid_t clk_id = CLOCK_MONOTONIC; -#endif - - if (clock_gettime(clk_id, &tp) != 0) { - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } - - return PyFloat_FromDouble(tp.tv_sec + tp.tv_nsec * 1e-9); -#else - PyErr_SetNone(PyExc_NotImplementedError); - return NULL; -#endif -} - -/* ------------------------------------------------------------------------- */ - -static PyMethodDef monotonic_methods[] = { - { "monotonic", (PyCFunction)monotonic, METH_NOARGS, 0 }, - { NULL, NULL } -}; - -static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - "_monotonic", /* m_name */ - NULL, /* m_doc */ - -1, /* m_size */ - monotonic_methods, /* m_methods */ - NULL, /* m_reload */ - NULL, /* m_traverse */ - NULL, /* m_clear */ - NULL, /* m_free */ -}; - -static PyObject * -moduleinit(void) -{ - PyObject *module; - - module = PyModule_Create(&moduledef); - - if (module == NULL) - return NULL; - - return module; -} - -PyMODINIT_FUNC PyInit__monotonic(void) -{ - return moduleinit(); -} - -/* ------------------------------------------------------------------------- */ - diff --git a/newrelic/common/package_version_utils.py b/newrelic/common/package_version_utils.py index 5b2886505c..da40f0dffa 100644 --- a/newrelic/common/package_version_utils.py +++ b/newrelic/common/package_version_utils.py @@ -69,7 +69,7 @@ def int_or_str(value): return version -@lru_cache() +@lru_cache def _get_package_version(name): module = sys.modules.get(name, None) version = None diff --git a/newrelic/common/stopwatch.py b/newrelic/common/stopwatch.py index 2305dda7e6..7c2edee4a8 100644 --- a/newrelic/common/stopwatch.py +++ b/newrelic/common/stopwatch.py @@ -20,36 +20,8 @@ import time -try: - # Python 3.3 and later implements PEP 418. Use the - # performance counter it provides which is monotonically - # increasing. - - default_timer = time.perf_counter - timer_implementation = "time.perf_counter()" - -except AttributeError: - try: - # Next try our own bundled back port of the monotonic() - # function. Python 3.3 does on Windows use a different - # clock for the performance counter, but the standard - # monotonic clock should suit our requirements okay. - - from newrelic.common._monotonic import monotonic as default_timer - - default_timer() - timer_implementation = "_monotonic.monotonic()" - - except (ImportError, NotImplementedError, OSError): - # If neither of the above, fallback to using the default - # timer from the timeit module. This will use the best - # resolution clock available on a particular platform, - # albeit that it isn't monotonically increasing. - - import timeit - - default_timer = timeit.default_timer - timer_implementation = "timeit.default_timer()" +default_timer = time.perf_counter +timer_implementation = "time.perf_counter()" # A timer class which deals with remembering the start time based on # wall clock time and duration based on a monotonic clock where diff --git a/pyproject.toml b/pyproject.toml index cb213b947c..810fdacf99 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,11 +105,10 @@ git_describe_command = 'git describe --dirty --tags --long --match "*.*.*"' [tool.ruff] output-format = "grouped" line-length = 120 -target-version = "py37" +target-version = "py38" force-exclude = true # Fixes issue with megalinter config preventing exclusion of files extend-exclude = [ "newrelic/packages/", - "setup.py", "newrelic/core/infinite_tracing_*_pb2.py", ] namespace-packages = ["testing_support"] @@ -201,7 +200,7 @@ ignore = [ "PT012", # pytest-raises-with-multiple-statements (too many to fix all at once) # Permanently disabled rules "PLC0415", # import-outside-top-level (intentionally used frequently) - "UP006", # non-pep585-annotation (not compatible with Python 3.7 or 3.8) + "UP006", # non-pep585-annotation (not compatible with Python 3.8) "D203", # incorrect-blank-line-before-class "D213", # multi-line-summary-second-line "ARG001", # unused-argument @@ -225,6 +224,13 @@ ignore = [ ] [tool.ruff.lint.per-file-ignores] +"setup.py" = [ + # Disabled rules in setup.py + # setup.py needs to not immediately crash on Python 2 to log error messages + "UP032", # f-string (Python 3+ syntax) + "B904", # raise-without-from-inside-except (Python 3+ syntax) + "E402", # module-import-not-at-top-of-file (intentional) +] "tests/*" = [ # Disabled rules in tests "S", # flake8-bandit (security checks are not necessary in tests) diff --git a/setup.py b/setup.py index a65e40d91e..2279145df3 100644 --- a/setup.py +++ b/setup.py @@ -15,15 +15,13 @@ import os import sys -from pathlib import Path - python_version = sys.version_info[:2] -if python_version >= (3, 7): +if python_version >= (3, 8): pass else: error_msg = ( - "The New Relic Python agent only supports Python 3.7+. We recommend upgrading to a newer version of Python." + "The New Relic Python agent only supports Python 3.8+. We recommend upgrading to a newer version of Python." ) try: @@ -35,14 +33,14 @@ (3, 4): "4.20.0.120", (3, 5): "5.24.0.153", (3, 6): "7.16.0.178", + (3, 7): "10.17.0", } last_supported_version = last_supported_version_lookup.get(python_version, None) if last_supported_version: - python_version_str = "%s.%s" % (python_version[0], python_version[1]) - error_msg += " The last agent version to support Python %s was v%s." % ( - python_version_str, - last_supported_version, + python_version_str = "{}.{}".format(python_version[0], python_version[1]) + error_msg += " The last agent version to support Python {} was v{}.".format( + python_version_str, last_supported_version ) except Exception: pass @@ -61,12 +59,9 @@ from distutils.command.build_ext import build_ext from distutils.core import Extension from distutils.errors import CCompilerError, DistutilsExecError, DistutilsPlatformError +from pathlib import Path - -if sys.platform == "win32": - build_ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError, IOError) -else: - build_ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError) +build_ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError, OSError) class BuildExtFailed(Exception): @@ -78,27 +73,27 @@ def run(self): try: build_ext.run(self) except DistutilsPlatformError: - raise BuildExtFailed() + raise BuildExtFailed def build_extension(self, ext): try: build_ext.build_extension(self, ext) except build_ext_errors: - raise BuildExtFailed() + raise BuildExtFailed -kwargs = dict( - name="newrelic", - setup_requires=["setuptools>=61.2", "setuptools_scm>=6.4,<10"], - license="Apache-2.0", -) +kwargs = { + "name": "newrelic", + "setup_requires": ["setuptools>=61.2", "setuptools_scm>=6.4,<10"], + "license": "Apache-2.0", +} if not with_setuptools: - script_directory = os.path.dirname(__file__) + script_directory = Path(__file__).parent if not script_directory: - script_directory = os.getcwd() + script_directory = Path.cwd() - readme_file = os.path.join(script_directory, "README.md") + readme_file = script_directory / "README.md" kwargs["scripts"] = ["scripts/newrelic-admin"] @@ -130,25 +125,22 @@ def build_extension(self, ext): "newrelic.samplers", ] - kwargs.update(dict( - python_requires=">=3.7", - zip_safe=False, - packages=packages, - package_data={ - "newrelic": ["newrelic.ini", "version.txt", "packages/urllib3/LICENSE.txt", "common/cacert.pem", "scripts/azure-prebuild.sh"], - }, - )) - - - -def with_librt(): - try: - if sys.platform.startswith("linux"): - import ctypes.util - - return ctypes.util.find_library("rt") - except Exception: - pass + kwargs.update( + { + "python_requires": ">=3.8", + "zip_safe": False, + "packages": packages, + "package_data": { + "newrelic": [ + "newrelic.ini", + "version.txt", + "packages/urllib3/LICENSE.txt", + "common/cacert.pem", + "scripts/azure-prebuild.sh", + ] + }, + } + ) def run_setup(with_extensions): @@ -159,18 +151,11 @@ def _run_setup(): kwargs_tmp = dict(kwargs) if with_extensions: - monotonic_libraries = [] - if with_librt(): - monotonic_libraries = ["rt"] - kwargs_tmp["ext_modules"] = [ Extension("newrelic.packages.wrapt._wrappers", ["newrelic/packages/wrapt/_wrappers.c"]), - Extension( - "newrelic.common._monotonic", ["newrelic/common/_monotonic.c"], libraries=monotonic_libraries - ), Extension("newrelic.core._thread_utilization", ["newrelic/core/_thread_utilization.c"]), ] - kwargs_tmp["cmdclass"] = dict(build_ext=optional_build_ext) + kwargs_tmp["cmdclass"] = {"build_ext": optional_build_ext} setup(**kwargs_tmp) diff --git a/tests/agent_features/test_attributes_in_action.py b/tests/agent_features/test_attributes_in_action.py index 0b24a3100a..4d12e8b059 100644 --- a/tests/agent_features/test_attributes_in_action.py +++ b/tests/agent_features/test_attributes_in_action.py @@ -14,6 +14,7 @@ import pytest import webtest +from testing_support.asgi_testing import AsgiTest from testing_support.fixtures import ( cat_enabled, dt_enabled, @@ -21,6 +22,7 @@ reset_core_stats_engine, validate_attributes, ) +from testing_support.sample_asgi_applications import normal_asgi_application from testing_support.validators.validate_browser_attributes import validate_browser_attributes from testing_support.validators.validate_error_event_attributes import validate_error_event_attributes from testing_support.validators.validate_error_event_attributes_outside_transaction import ( @@ -45,13 +47,6 @@ from newrelic.api.wsgi_application import wsgi_application from newrelic.common.object_names import callable_name -try: - from testing_support.asgi_testing import AsgiTest - from testing_support.sample_asgi_applications import normal_asgi_application -except SyntaxError: - normal_asgi_application = None - - URL_PARAM = "some_key" URL_PARAM2 = "second_key" REQUEST_URL = f"/?{URL_PARAM}=someval&{URL_PARAM2}=anotherval" @@ -160,12 +155,7 @@ def normal_wsgi_application(environ, start_response): return [output] -application_params = [normal_wsgi_application] -if normal_asgi_application: - application_params.append(normal_asgi_application) - - -@pytest.fixture(scope="module", params=application_params) +@pytest.fixture(scope="module", params=[normal_wsgi_application, normal_asgi_application]) def normal_application(request): if request.param is normal_wsgi_application: return webtest.TestApp(normal_wsgi_application) diff --git a/tests/framework_flask/conftest.py b/tests/framework_flask/conftest.py index 872de5b53c..f2881d0c75 100644 --- a/tests/framework_flask/conftest.py +++ b/tests/framework_flask/conftest.py @@ -15,18 +15,11 @@ import platform import pytest -from flask import __version__ as flask_version # required for python 3.7 in lieu of get_package_version_tuple +from testing_support.fixtures import collector_agent_registration_fixture, collector_available_fixture from newrelic.common.package_version_utils import get_package_version_tuple -try: - FLASK_VERSION = tuple(int(v) for v in flask_version.split(".")) -except: - # This does not work for Python 3.7 for v2.2.5 - # This only works for flaskmaster - FLASK_VERSION = get_package_version_tuple("flask") - -from testing_support.fixtures import collector_agent_registration_fixture, collector_available_fixture +FLASK_VERSION = get_package_version_tuple("flask") _default_settings = { "package_reporting.enabled": False, # Turn off package reporting for testing as it causes slow downs. diff --git a/tests/framework_starlette/test_application.py b/tests/framework_starlette/test_application.py index 55f751e9a3..cd5668fcb8 100644 --- a/tests/framework_starlette/test_application.py +++ b/tests/framework_starlette/test_application.py @@ -120,7 +120,7 @@ def test_exception_in_middleware(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 sys.version_info[0:2] > (3, 7) and starlette_version >= (0, 15, 0) and starlette_version < (0, 17, 0): + if (0, 15, 0) <= starlette_version < (0, 17, 0): from anyio._backends._asyncio import ExceptionGroup exc_type = ExceptionGroup diff --git a/tests/framework_starlette/test_bg_tasks.py b/tests/framework_starlette/test_bg_tasks.py index 5c339e7a49..15b37eafb8 100644 --- a/tests/framework_starlette/test_bg_tasks.py +++ b/tests/framework_starlette/test_bg_tasks.py @@ -85,15 +85,9 @@ def _test(): assert response.status == 200 # The bug was fixed in version 0.21.0 but re-occured in 0.23.1. - # The bug was also not present on 0.20.1 to 0.23.1 if using Python3.7. + # The bug was also not present on 0.20.1 to 0.23.1 if using Python 3.7. # The bug was fixed again in version 0.29.0 - BUG_COMPLETELY_FIXED = any( - ( - (0, 21, 0) <= starlette_version < (0, 23, 1), - (0, 20, 1) <= starlette_version < (0, 23, 1) and sys.version_info[:2] > (3, 7), - starlette_version >= (0, 29, 0), - ) - ) + BUG_COMPLETELY_FIXED = any(((0, 20, 1) <= starlette_version < (0, 23, 1), starlette_version >= (0, 29, 0))) BUG_PARTIALLY_FIXED = any( ((0, 20, 1) <= starlette_version < (0, 21, 0), (0, 23, 1) <= starlette_version < (0, 29, 0)) ) diff --git a/tox.ini b/tox.ini index d40901288f..1a20c1243a 100644 --- a/tox.ini +++ b/tox.ini @@ -16,7 +16,7 @@ ; framework_aiohttp-aiohttp01: aiohttp<2 ; framework_aiohttp-aiohttp0202: aiohttp<2.3 ; 3. Python version required. Uses the standard tox definitions. (https://tox.readthedocs.io/en/latest/config.html#tox-environments) -; Examples: py37,py38,py39,pypy310 +; Examples: py38,py39,py310,py311,py312,py313,pypy310 ; 4. Library and version (Optional). Used when testing multiple versions of the library, and may be omitted when only testing a single version. ; Versions should be specified with 2 digits per version number, so <3 becomes 02 and <3.5 becomes 0304. latest and master are also acceptable versions. ; Examples: uvicorn03, CherryPy0302, uvicornlatest @@ -29,13 +29,13 @@ ; Examples: with_extensions, without_extensions ; envlist = ; python-agent_features-pypy310-without_extensions, -; python-agent_streaming-py37-{with,without}_extensions, +; python-agent_streaming-py312-{with,without}_extensions, ; ; Full Format: ; services_required-tests_folder-python_version-library_and_version[optional]-with/without_c_extensions[optional] ; ; Full Examples: -; - memcached-datastore_bmemcached-py37-memcached030 +; - memcached-datastore_bmemcached-py313-memcached030 ; - python-agent_unittests-py38-with_extensions ; - python-adapter_gevent-py39 @@ -45,157 +45,150 @@ setupdir = {toxinidir} skip_missing_interpreters = false envlist = cassandra-datastore_cassandradriver-{py38,py39,py310,py311,py312,pypy310}-cassandralatest, - elasticsearchserver07-datastore_elasticsearch-{py37,py38,py39,py310,py311,py312,py313,pypy310}-elasticsearch07, - elasticsearchserver08-datastore_elasticsearch-{py37,py38,py39,py310,py311,py312,py313,pypy310}-elasticsearch08, - firestore-datastore_firestore-{py37,py38,py39,py310,py311,py312,py313}, + elasticsearchserver07-datastore_elasticsearch-{py38,py39,py310,py311,py312,py313,pypy310}-elasticsearch07, + elasticsearchserver08-datastore_elasticsearch-{py38,py39,py310,py311,py312,py313,pypy310}-elasticsearch08, + firestore-datastore_firestore-{py38,py39,py310,py311,py312,py313}, grpc-framework_grpc-{py39,py310,py311,py312,py313}-grpclatest, kafka-messagebroker_confluentkafka-py39-confluentkafka{0108,0107,0106}, - kafka-messagebroker_confluentkafka-{py37,py38,py39,py310,py311,py312,py313}-confluentkafkalatest, - kafka-messagebroker_kafkapython-{py37,py38,py39,py310,py311,py312,py313,pypy310}-kafkapythonlatest, + kafka-messagebroker_confluentkafka-{py38,py39,py310,py311,py312,py313}-confluentkafkalatest, + kafka-messagebroker_kafkapython-{py38,py39,py310,py311,py312,py313,pypy310}-kafkapythonlatest, kafka-messagebroker_kafkapython-{py38,py39,py310,py311,py312,py313,pypy310}-kafkapythonnglatest, memcached-datastore_aiomcache-{py38,py39,py310,py311,py312,py313}, - memcached-datastore_bmemcached-{py37,py38,py39,py310,py311,py312,py313}, - memcached-datastore_memcache-{py37,py38,py39,py310,py311,py312,py313,pypy310}-memcached01, - memcached-datastore_pylibmc-py37, - memcached-datastore_pymemcache-{py37,py38,py39,py310,py311,py312,py313,pypy310}, - mongodb8-datastore_motor-{py37,py38,py39,py310,py311,py312,py313}-motorlatest, - mongodb3-datastore_pymongo-{py37,py38,py39,py310,py311,py312}-pymongo03, - mongodb8-datastore_pymongo-{py37,py38,py39,py310,py311,py312,py313,pypy310}-pymongo04, + memcached-datastore_bmemcached-{py38,py39,py310,py311,py312,py313}, + memcached-datastore_memcache-{py38,py39,py310,py311,py312,py313,pypy310}-memcached01, + memcached-datastore_pylibmc-{py38,py39,py310,py311}, + memcached-datastore_pymemcache-{py38,py39,py310,py311,py312,py313,pypy310}, + mongodb8-datastore_motor-{py38,py39,py310,py311,py312,py313}-motorlatest, + mongodb3-datastore_pymongo-{py38,py39,py310,py311,py312}-pymongo03, + mongodb8-datastore_pymongo-{py38,py39,py310,py311,py312,py313,pypy310}-pymongo04, ; aiomysql tests on PyPy disabled for now due to issues building cryptography - mysql-datastore_aiomysql-{py37,py38,py39,py310,py311,py312,py313}, + mysql-datastore_aiomysql-{py38,py39,py310,py311,py312,py313}, mssql-datastore_pymssql-pymssqllatest-{py39,py310,py311,py312,py313}, - mssql-datastore_pymssql-pymssql020301-{py37,py38}, - mysql-datastore_mysql-mysqllatest-{py37,py38,py39,py310,py311,py312,py313}, + mssql-datastore_pymssql-pymssql020301-py38, + mysql-datastore_mysql-mysqllatest-{py38,py39,py310,py311,py312,py313}, mysql-datastore_mysqldb-{py38,py39,py310,py311,py312,py313}, ; pymysql tests on PyPy disabled for now due to issues building cryptography - mysql-datastore_pymysql-{py37,py38,py39,py310,py311,py312,py313}, + mysql-datastore_pymysql-{py38,py39,py310,py311,py312,py313}, oracledb-datastore_oracledb-{py39,py310,py311,py312,py313}-oracledblatest, oracledb-datastore_oracledb-{py39,py313}-oracledb02, oracledb-datastore_oracledb-{py39,py312}-oracledb01, - nginx-external_httpx-{py37,py38,py39,py310,py311,py312,py313}, - postgres16-datastore_asyncpg-{py37,py38,py39,py310,py311,py312,py313}, + nginx-external_httpx-{py38,py39,py310,py311,py312,py313}, + postgres16-datastore_asyncpg-{py38,py39,py310,py311,py312,py313}, postgres16-datastore_psycopg-{py38,py39,py310,py311,py312,py313,pypy310}-psycopglatest, postgres16-datastore_psycopg-py312-psycopg_{purepython,binary,compiled}0301, - postgres16-datastore_psycopg2-{py37,py38,py39,py310,py311,py312}-psycopg2latest, - postgres16-datastore_psycopg2cffi-{py37,py38,py39,py310,py311,py312}-psycopg2cffilatest, - postgres16-datastore_pyodbc-{py37,py38,py39,py310,py311,py312,py313}-pyodbclatest, - postgres9-datastore_postgresql-{py37,py38,py39,py310,py311,py312,py313}, - python-adapter_asgiref-{py37,py38,py39,py310,py311,py312,py313,pypy310}-asgireflatest, + postgres16-datastore_psycopg2-{py38,py39,py310,py311,py312}-psycopg2latest, + postgres16-datastore_psycopg2cffi-{py38,py39,py310,py311,py312}-psycopg2cffilatest, + postgres16-datastore_pyodbc-{py38,py39,py310,py311,py312,py313}-pyodbclatest, + postgres9-datastore_postgresql-{py38,py39,py310,py311,py312,py313}, + python-adapter_asgiref-{py38,py39,py310,py311,py312,py313,pypy310}-asgireflatest, python-adapter_asgiref-py310-asgiref{0303,0304,0305,0306,0307}, - python-adapter_cheroot-{py37,py38,py39,py310,py311,py312,py313}, - python-adapter_daphne-{py37,py38,py39,py310,py311,py312,py313}-daphnelatest, - python-adapter_gevent-{py37,py38,py310,py311,py312,py313}, - python-adapter_gunicorn-{py37,py38,py39,py310,py311,py312,py313}-aiohttp03-gunicornlatest, + python-adapter_cheroot-{py38,py39,py310,py311,py312,py313}, + python-adapter_daphne-{py38,py39,py310,py311,py312,py313}-daphnelatest, + python-adapter_gevent-{py38,py310,py311,py312,py313}, + python-adapter_gunicorn-{py38,py39,py310,py311,py312,py313}-aiohttp03-gunicornlatest, python-adapter_hypercorn-{py38,py39,py310,py311,py312,py313}-hypercornlatest, python-adapter_hypercorn-py38-hypercorn{0010,0011,0012,0013}, ; mcp tests on PyPy disabled for now due to issues building cryptography python-adapter_mcp-{py310,py311,py312,py313}, - python-adapter_uvicorn-{py37,py38,py39,py310,py311,py312,py313}-uvicornlatest, + python-adapter_uvicorn-{py38,py39,py310,py311,py312,py313}-uvicornlatest, python-adapter_uvicorn-py38-uvicorn014, - python-adapter_waitress-{py37,py38,py39,py310,py311,py312,py313}-waitresslatest, - python-agent_features-{py37,py38,py39,py310,py311,py312,py313}-{with,without}_extensions, + python-adapter_waitress-{py38,py39,py310,py311,py312,py313}-waitresslatest, + python-agent_features-{py38,py39,py310,py311,py312,py313}-{with,without}_extensions, python-agent_features-pypy310-without_extensions, - python-agent_streaming-{py37,py38,py39,py310,py311,py312,py313}-protobuf06-{with,without}_extensions, + python-agent_streaming-{py38,py39,py310,py311,py312,py313}-protobuf06-{with,without}_extensions, python-agent_streaming-py39-protobuf{03,0319,04,05}-{with,without}_extensions, - python-agent_unittests-{py37,py38,py39,py310,py311,py312,py313}-{with,without}_extensions, + python-agent_unittests-{py38,py39,py310,py311,py312,py313}-{with,without}_extensions, python-agent_unittests-pypy310-without_extensions, - python-application_celery-{py37,py38,py39,py310,py311,py312,py313,pypy310}-celerylatest, + python-application_celery-{py38,py39,py310,py311,py312,py313,pypy310}-celerylatest, python-application_celery-py311-celery{0504,0503,0502}, - python-component_djangorestframework-{py37,py38,py39,py310,py311,py312,py313}-djangorestframeworklatest, + python-component_djangorestframework-{py38,py39,py310,py311,py312,py313}-djangorestframeworklatest, python-component_flask_rest-{py38,py39,py310,py311,py312,py313,pypy310}-flaskrestxlatest, - python-component_flask_rest-py37-flaskrestx110, - python-component_graphqlserver-{py37,py38,py39,py310,py311,py312}, + python-component_graphqlserver-{py38,py39,py310,py311,py312}, ;; Tests need to be updated to support newer graphql-server/sanic versions ; python-component_graphqlserver-py313, - python-component_tastypie-{py37,py38,py39,py310,py311,py312,py313,pypy310}-tastypielatest, - python-coroutines_asyncio-{py37,py38,py39,py310,py311,py312,py313,pypy310}, - python-cross_agent-{py37,py38,py39,py310,py311,py312,py313}-{with,without}_extensions, - python-datastore_sqlite-{py37,py38,py39,py310,py311,py312,py313,pypy310}, + python-component_tastypie-{py38,py39,py310,py311,py312,py313,pypy310}-tastypielatest, + python-coroutines_asyncio-{py38,py39,py310,py311,py312,py313,pypy310}, + python-cross_agent-{py38,py39,py310,py311,py312,py313}-{with,without}_extensions, + python-datastore_sqlite-{py38,py39,py310,py311,py312,py313,pypy310}, python-external_aiobotocore-{py38,py39,py310,py311,py312,py313}-aiobotocorelatest, python-external_botocore-{py38,py39,py310,py311,py312,py313}-botocorelatest, python-external_botocore-{py311}-botocorelatest-langchain, python-external_botocore-py310-botocore0125, python-external_botocore-py311-botocore0128, - python-external_feedparser-{py37,py38,py39,py310,py311,py312,py313}-feedparser06, - python-external_http-{py37,py38,py39,py310,py311,py312,py313}, - python-external_httplib-{py37,py38,py39,py310,py311,py312,py313,pypy310}, - python-external_httplib2-{py37,py38,py39,py310,py311,py312,py313,pypy310}, + python-external_feedparser-{py38,py39,py310,py311,py312,py313}-feedparser06, + python-external_http-{py38,py39,py310,py311,py312,py313}, + python-external_httplib-{py38,py39,py310,py311,py312,py313,pypy310}, + python-external_httplib2-{py38,py39,py310,py311,py312,py313,pypy310}, python-external_pyzeebe-{py39,py310,py311,py312,pypy310}, - python-external_requests-{py37,py38,py39,py310,py311,py312,py313,pypy310}, - python-external_urllib3-{py37,py38,py39,py310,py311,py312,py313,pypy310}-urllib3latest, - python-external_urllib3-{py37,py312,py313,pypy310}-urllib30126, - python-framework_aiohttp-{py37,py38,py39,py310,py311,py312,py313,pypy310}-aiohttp03, - python-framework_ariadne-{py37,py38,py39,py310,py311,py312,py313}-ariadnelatest, - python-framework_ariadne-py37-ariadne{0011,0012,0013}, + python-external_requests-{py38,py39,py310,py311,py312,py313,pypy310}, + python-external_urllib3-{py38,py39,py310,py311,py312,py313,pypy310}-urllib3latest, + python-external_urllib3-{py312,py313,pypy310}-urllib30126, + python-framework_aiohttp-{py38,py39,py310,py311,py312,py313,pypy310}-aiohttp03, + python-framework_ariadne-{py38,py39,py310,py311,py312,py313}-ariadnelatest, python-framework_azurefunctions-{py39,py310,py311,py312}, - python-framework_bottle-{py37,py38,py39,py310,py311,py312,py313,pypy310}-bottle0012, - python-framework_cherrypy-{py37,py38,py39,py310,py311,py312,py313,pypy310}-CherryPylatest, - python-framework_django-{py37,py38,py39,py310,py311,py312,py313}-Djangolatest, - python-framework_django-{py39}-Django{0202,0300,0301,0302,0401}, + python-framework_bottle-{py38,py39,py310,py311,py312,py313,pypy310}-bottle0012, + python-framework_cherrypy-{py38,py39,py310,py311,py312,py313,pypy310}-CherryPylatest, + python-framework_django-{py38,py39,py310,py311,py312,py313}-Djangolatest, + python-framework_django-py39-Django{0202,0300,0301,0302,0401}, python-framework_falcon-{py39,py310,py311,py312,py313,pypy310}-falconlatest, - python-framework_falcon-{py37,py38}-falcon0410, + python-framework_falcon-py38-falcon0410, python-framework_falcon-{py39,py310,py311,py312,py313,pypy310}-falconmaster, - python-framework_fastapi-{py37,py38,py39,py310,py311,py312,py313}, - python-framework_flask-py37-flask020205, + python-framework_fastapi-{py38,py39,py310,py311,py312,py313}, python-framework_flask-{py38,py39,py310,py311,py312,pypy310}-flask02, - ; python-framework_flask-py38-flaskmaster fails, even with Flask-Compress<1.16 and coverage==7.61 for py37,py38 + ; python-framework_flask-py38-flaskmaster fails, even with Flask-Compress<1.16 and coverage==7.61 for py38 python-framework_flask-py38-flasklatest, ; flaskmaster tests disabled until they can be fixed python-framework_flask-{py39,py310,py311,py312,py313,pypy310}-flask{latest}, - python-framework_graphene-{py37,py38,py39,py310,py311,py312,py313}-graphenelatest, - python-component_graphenedjango-{py37,py38,py39,py310,py311,py312,py313}-graphenedjangolatest, - python-framework_graphql-{py37,py38,py39,py310,py311,py312,py313,pypy310}-graphql03, - python-framework_graphql-{py37,py38,py39,py310,py311,py312,py313,pypy310}-graphql{latest}, - python-framework_graphql-py37-graphql{0301,0302}, - python-framework_pyramid-{py37,py38,py39,py310,py311,py312,py313,pypy310}-Pyramidlatest, - python-framework_pyramid-{py37,py38,py39,py310,py311,py312,py313,pypy310}-Pyramid0110-cornice, - python-framework_sanic-{py37,py38}-sanic2406, + python-framework_graphene-{py38,py39,py310,py311,py312,py313}-graphenelatest, + python-component_graphenedjango-{py38,py39,py310,py311,py312,py313}-graphenedjangolatest, + python-framework_graphql-{py38,py39,py310,py311,py312,py313,pypy310}-graphql03, + python-framework_graphql-{py38,py39,py310,py311,py312,py313,pypy310}-graphqllatest, + python-framework_pyramid-{py38,py39,py310,py311,py312,py313,pypy310}-Pyramidlatest, + python-framework_pyramid-{py38,py39,py310,py311,py312,py313,pypy310}-Pyramid0110-cornice, + python-framework_sanic-{py38}-sanic2406, python-framework_sanic-{py39,py310,py311,py312,py313,pypy310}-saniclatest, python-framework_sanic-{py38,pypy310}-sanic2290, python-framework_starlette-{py310,pypy310}-starlette{0014,0015,0019,0028}, - python-framework_starlette-{py37,py38,py39,py310,py311,py312,py313,pypy310}-starlettelatest, - python-framework_starlette-{py37,py38}-starlette002001, + python-framework_starlette-{py38,py39,py310,py311,py312,py313,pypy310}-starlettelatest, + python-framework_starlette-{py38}-starlette002001, python-framework_strawberry-{py38,py39,py310,py311,py312}-strawberry02352, - python-framework_strawberry-{py37,py38,py39,py310,py311,py312,py313}-strawberrylatest, + python-framework_strawberry-{py38,py39,py310,py311,py312,py313}-strawberrylatest, python-framework_tornado-{py38,py39,py310,py311,py312,py313}-tornadolatest, python-framework_tornado-{py310,py311,py312,py313}-tornadomaster, - python-logger_logging-{py37,py38,py39,py310,py311,py312,py313,pypy310}, - python-logger_loguru-{py37,py38,py39,py310,py311,py312,py313,pypy310}-logurulatest, - python-logger_structlog-{py37,py38,py39,py310,py311,py312,py313,pypy310}-structloglatest, + python-logger_logging-{py38,py39,py310,py311,py312,py313,pypy310}, + python-logger_loguru-{py38,py39,py310,py311,py312,py313,pypy310}-logurulatest, + python-logger_structlog-{py38,py39,py310,py311,py312,py313,pypy310}-structloglatest, python-mlmodel_gemini-{py39,py310,py311,py312,py313}, python-mlmodel_langchain-{py39,py310,py311,py312}, ;; Package not ready for Python 3.13 (uses an older version of numpy) ; python-mlmodel_langchain-py313, - python-mlmodel_openai-openai0-{py37,py38,py39,py310,py311,py312}, + python-mlmodel_openai-openai0-{py38,py39,py310,py311,py312}, python-mlmodel_openai-openai107-py312, - python-mlmodel_openai-openailatest-{py37,py38,py39,py310,py311,py312,py313}, - python-mlmodel_sklearn-{py37}-scikitlearn0101, + python-mlmodel_openai-openailatest-{py38,py39,py310,py311,py312,py313}, python-mlmodel_sklearn-{py38,py39,py310,py311,py312,py313}-scikitlearnlatest, - python-template_genshi-{py37,py38,py39,py310,py311,py312,py313}-genshilatest, + python-template_genshi-{py38,py39,py310,py311,py312,py313}-genshilatest, python-template_jinja2-{py38,py39,py310,py311,py312,py313}-jinja2latest, - python-template_jinja2-py37-jinja2030103, - python-template_mako-{py37,py38,py39,py310,py311,py312,py313}, - rabbitmq-messagebroker_pika-{py37,py38,py39,py310,py311,py312,py313,pypy310}-pikalatest, + python-template_mako-{py38,py39,py310,py311,py312,py313}, + rabbitmq-messagebroker_pika-{py38,py39,py310,py311,py312,py313,pypy310}-pikalatest, rabbitmq-messagebroker_kombu-{py38,py39,py310,py311,py312,py313,pypy310}-kombulatest, rabbitmq-messagebroker_kombu-{py38,py39,py310,pypy310}-kombu050204, - redis-datastore_redis-{py37,py38,py39,py310,py311,pypy310}-redis04, + redis-datastore_redis-{py38,py39,py310,py311,pypy310}-redis04, redis-datastore_redis-{py38,py39,py310,py311,py312,pypy310}-redis05, redis-datastore_redis-{py38,py39,py310,py311,py312,py313,pypy310}-redislatest, - rediscluster-datastore_rediscluster-{py37,py312,py313,pypy310}-redislatest, + rediscluster-datastore_rediscluster-{py312,py313,pypy310}-redislatest, valkey-datastore_valkey-{py38,py39,py310,py311,py312,py313,pypy310}-valkeylatest, - solr-datastore_pysolr-{py37,py38,py39,py310,py311,py312,py313,pypy310}, + solr-datastore_pysolr-{py38,py39,py310,py311,py312,py313,pypy310}, [testenv] deps = # Base Dependencies {py39,py310,py311,py312,py313,pypy310}: pytest==8.4.1 py38: pytest==8.3.5 - py37: pytest==7.4.4 iniconfig coverage {py39,py310,py311,py312,py313,pypy310}: WebTest==3.0.6 + py38: WebTest==3.0.1 py313: legacy-cgi==2.6.1 # cgi was removed from the stdlib in 3.13, and is required for WebTest - {py37,py38}: WebTest==3.0.1 # Test Suite Dependencies adapter_asgiref-asgireflatest: asgiref @@ -238,14 +231,12 @@ deps = application_celery-celery0504: celery[pytest]<5.5 application_celery-celery0503: celery[pytest]<5.4 application_celery-celery0502: celery[pytest]<5.3 - application_celery-{py37,pypy310}: importlib-metadata<5.0 + application_celery-pypy310: importlib-metadata<5.0 mlmodel_sklearn: pandas mlmodel_sklearn: protobuf mlmodel_sklearn: numpy mlmodel_sklearn-scikitlearnlatest: scikit-learn mlmodel_sklearn-scikitlearnlatest: scipy - mlmodel_sklearn-scikitlearn0101: scikit-learn<1.1 - mlmodel_sklearn-scikitlearn0101: scipy<1.11.0 component_djangorestframework-djangorestframeworklatest: Django component_djangorestframework-djangorestframeworklatest: djangorestframework component_flask_rest: flask-restful @@ -253,11 +244,6 @@ deps = component_flask_rest: itsdangerous component_flask_rest-flaskrestxlatest: flask component_flask_rest-flaskrestxlatest: flask-restx - ; flask-restx only supports Flask v3 after flask-restx v1.3.0 - component_flask_rest-flaskrestx110: Flask<3.0 - component_flask_rest-flaskrestx110: flask-restx<1.2 - component_flask_rest-flaskrestx051: Flask<3.0 - component_flask_rest-flaskrestx051: flask-restx<1.0 component_graphqlserver: graphql-server[sanic,flask]==3.0.0b5 component_graphqlserver: sanic>20 component_graphqlserver: Flask @@ -266,7 +252,7 @@ deps = component_tastypie-tastypielatest: django-tastypie component_tastypie-tastypielatest: django<4.1 component_tastypie-tastypielatest: asgiref<3.7.1 # asgiref==3.7.1 only suppport Python 3.10+ - coroutines_asyncio-{py37,py38,py39,py310,py311,py312,py313}: uvloop + coroutines_asyncio-{py38,py39,py310,py311,py312,py313}: uvloop cross_agent: requests datastore_asyncpg: asyncpg datastore_aiomcache: aiomcache @@ -340,9 +326,6 @@ deps = framework_aiohttp-aiohttp03: aiohttp<4 framework_aiohttp-aiohttp030900rc0: aiohttp==3.9.0rc0 framework_ariadne-ariadnelatest: ariadne - framework_ariadne-ariadne0011: ariadne<0.12 - framework_ariadne-ariadne0012: ariadne<0.13 - framework_ariadne-ariadne0013: ariadne<0.14 framework_azurefunctions: azure-functions framework_azurefunctions: requests framework_bottle-bottle0012: bottle<0.13.0 @@ -365,8 +348,6 @@ deps = framework_flask: Flask-Compress framework_flask-flask02: flask[async]<3 framework_flask-flask02: jinja2<3.1.3 - framework_flask-flask020205: jinja2<3.1.3 - framework_flask-flask020205: flask[async]<2.3 framework_flask-flasklatest: markupsafe framework_flask-flasklatest: jinja2 framework_flask-flasklatest: flask[async] @@ -375,11 +356,8 @@ deps = framework_flask-flaskmaster: asgiref framework_graphene-graphenelatest: graphene component_graphenedjango-graphenedjangolatest: graphene-django - framework_graphql-graphqllatest: graphql-core framework_graphql-graphql03: graphql-core<4 - framework_graphql-graphql0301: graphql-core<3.2 - framework_graphql-graphql0302: graphql-core<3.3 - framework_graphql-graphqlmaster: https://github.com/graphql-python/graphql-core/archive/main.zip + framework_graphql-graphqllatest: graphql-core framework_grpc-grpclatest: protobuf framework_grpc-grpclatest: grpcio framework_grpc-grpclatest: grpcio-tools @@ -446,7 +424,6 @@ deps = messagebroker_kafkapython-kafkapythonlatest: kafka-python<2.1 template_genshi-genshilatest: genshi template_jinja2-jinja2latest: Jinja2 - template_jinja2-jinja2030103: Jinja2<3.1.4 template_mako: mako setenv =