diff --git a/.github/containers/Dockerfile b/.github/containers/Dockerfile index 3f370a4a45..b3016548d7 100644 --- a/.github/containers/Dockerfile +++ b/.github/containers/Dockerfile @@ -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 diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 4bfbaf5e94..6f87e39875 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -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" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3948a41b94..801efacc94 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -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 @@ -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 diff --git a/asv.conf.json b/asv.conf.json index 203d52c887..ca6902e314 100644 --- a/asv.conf.json +++ b/asv.conf.json @@ -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", diff --git a/newrelic/admin/__init__.py b/newrelic/admin/__init__.py index 61f3995a3f..fe6433a23e 100644 --- a/newrelic/admin/__init__.py +++ b/newrelic/admin/__init__.py @@ -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" diff --git a/newrelic/common/package_version_utils.py b/newrelic/common/package_version_utils.py index da40f0dffa..38c85057d0 100644 --- a/newrelic/common/package_version_utils.py +++ b/newrelic/common/package_version_utils.py @@ -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 @@ -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 diff --git a/newrelic/config.py b/newrelic/config.py index 4b8627772d..84f642527d 100644 --- a/newrelic/config.py +++ b/newrelic/config.py @@ -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" @@ -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" diff --git a/newrelic/core/config.py b/newrelic/core/config.py index 8cfdeda0ae..c6b2d4233e 100644 --- a/newrelic/core/config.py +++ b/newrelic/core/config.py @@ -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}" diff --git a/newrelic/hooks/logger_structlog.py b/newrelic/hooks/logger_structlog.py index 8d9ba3cc5d..66d7102505 100644 --- a/newrelic/hooks/logger_structlog.py +++ b/newrelic/hooks/logger_structlog.py @@ -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. diff --git a/pyproject.toml b/pyproject.toml index 2dbdb34837..435cf190db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", @@ -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/", diff --git a/setup.py b/setup.py index 4cccb1e437..486ffd778b 100644 --- a/setup.py +++ b/setup.py @@ -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: @@ -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) @@ -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": { diff --git a/tests/agent_unittests/test_package_version_utils.py b/tests/agent_unittests/test_package_version_utils.py index 8add829195..4de504b052 100644 --- a/tests/agent_unittests/test_package_version_utils.py +++ b/tests/agent_unittests/test_package_version_utils.py @@ -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+") @@ -101,7 +96,6 @@ 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. @@ -109,14 +103,6 @@ def test_importlib_dot_metadata(): 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(): @@ -124,15 +110,6 @@ def test_mapping_import_to_distribution_packages(): 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) diff --git a/tests/framework_starlette/test_application.py b/tests/framework_starlette/test_application.py index cd5668fcb8..2005e53c2c 100644 --- a/tests/framework_starlette/test_application.py +++ b/tests/framework_starlette/test_application.py @@ -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 diff --git a/tests/framework_strawberry/_target_schema_async.py b/tests/framework_strawberry/_target_schema_async.py index 72234e79a6..e85ef8ae30 100644 --- a/tests/framework_strawberry/_target_schema_async.py +++ b/tests/framework_strawberry/_target_schema_async.py @@ -14,8 +14,6 @@ from __future__ import annotations -from typing import List - import strawberry try: @@ -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) diff --git a/tests/framework_strawberry/_target_schema_sync.py b/tests/framework_strawberry/_target_schema_sync.py index b4559763e1..1504022af5 100644 --- a/tests/framework_strawberry/_target_schema_sync.py +++ b/tests/framework_strawberry/_target_schema_sync.py @@ -14,7 +14,7 @@ from __future__ import annotations -from typing import List, Union +from typing import Union import strawberry @@ -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 = [ @@ -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) diff --git a/tests/mlmodel_sklearn/test_inference_events.py b/tests/mlmodel_sklearn/test_inference_events.py index d1fc0762b0..92b01727b9 100644 --- a/tests/mlmodel_sklearn/test_inference_events.py +++ b/tests/mlmodel_sklearn/test_inference_events.py @@ -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"}, @@ -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) diff --git a/tox.ini b/tox.ini index 71206358ae..6df9657d02 100644 --- a/tox.ini +++ b/tox.ini @@ -20,7 +20,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: py38,py39,py310,py311,py312,py313,py314,py314t,pypy311 +; Examples: py39,py310,py311,py312,py313,py314,py314t,pypy311 ; 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 @@ -40,7 +40,7 @@ ; ; Full Examples: ; - memcached-datastore_bmemcached-py313-memcached030 -; - linux-agent_unittests-py38-with_extensions +; - linux-agent_unittests-py314-with_extensions ; - python-adapter_gevent-py39 [tox] @@ -51,13 +51,13 @@ uv_seed = true skip_missing_interpreters = false envlist = # Linux Core Agent Test Suite - {linux,linux_arm64}-agent_features-{py38,py39,py310,py311,py312,py313,py314}-{with,without}_extensions, + {linux,linux_arm64}-agent_features-{py39,py310,py311,py312,py313,py314}-{with,without}_extensions, {linux,linux_arm64}-agent_features-pypy311-without_extensions, - {linux,linux_arm64}-agent_streaming-{py38,py39,py310,py311,py312,py313,py314}-protobuf06-{with,without}_extensions, + {linux,linux_arm64}-agent_streaming-{py39,py310,py311,py312,py313,py314}-protobuf06-{with,without}_extensions, {linux,linux_arm64}-agent_streaming-py39-protobuf{03,0319,04,05}-{with,without}_extensions, - {linux,linux_arm64}-agent_unittests-{py38,py39,py310,py311,py312,py313,py314}-{with,without}_extensions, + {linux,linux_arm64}-agent_unittests-{py39,py310,py311,py312,py313,py314}-{with,without}_extensions, {linux,linux_arm64}-agent_unittests-pypy311-without_extensions, - {linux,linux_arm64}-cross_agent-{py38,py39,py310,py311,py312,py313,py314}-{with,without}_extensions, + {linux,linux_arm64}-cross_agent-{py39,py310,py311,py312,py313,py314}-{with,without}_extensions, {linux,linux_arm64}-cross_agent-pypy311-without_extensions, # Windows Core Agent Test Suite @@ -68,119 +68,110 @@ envlist = {windows,windows_arm64}-cross_agent-{py313,py314,py314t}-{with,without}_extensions, # Integration Tests (only run on Linux) - cassandra-datastore_cassandradriver-py38-cassandra032903, cassandra-datastore_cassandradriver-{py39,py310,py311,py312,pypy311}-cassandralatest, - elasticsearchserver07-datastore_elasticsearch-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-elasticsearch07, - elasticsearchserver08-datastore_elasticsearch-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-elasticsearch08, - firestore-datastore_firestore-{py38,py39,py310,py311,py312,py313,py314,py314t}, + elasticsearchserver07-datastore_elasticsearch-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-elasticsearch07, + elasticsearchserver08-datastore_elasticsearch-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-elasticsearch08, + firestore-datastore_firestore-{py39,py310,py311,py312,py313,py314,py314t}, grpc-framework_grpc-{py39,py310,py311,py312,py313,py314,py314t}-grpclatest, kafka-messagebroker_confluentkafka-py39-confluentkafka{0108,0107,0106}, - kafka-messagebroker_confluentkafka-{py38,py39,py310,py311,py312,py313}-confluentkafkalatest, + kafka-messagebroker_confluentkafka-{py39,py310,py311,py312,py313}-confluentkafkalatest, ;; Package not ready for Python 3.14 (confluent-kafka wheels not released) ; kafka-messagebroker_confluentkafka-{py314,py314t}-confluentkafkalatest, - kafka-messagebroker_kafkapython-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-kafkapythonlatest, - kafka-messagebroker_kafkapython-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-kafkapythonnglatest, - memcached-datastore_aiomcache-{py38,py39,py310,py311,py312,py313,py314,py314t}, - memcached-datastore_bmemcached-{py38,py39,py310,py311,py312,py313,py314,py314t}, - memcached-datastore_memcache-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-memcached01, - memcached-datastore_pylibmc-{py38,py39,py310,py311}, - memcached-datastore_pymemcache-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}, - mongodb8-datastore_motor-{py38,py39,py310,py311,py312,py313,py314,py314t}-motorlatest, - mongodb3-datastore_pymongo-{py38,py39,py310,py311,py312}-pymongo03, - mongodb8-datastore_pymongo-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-pymongo04, - mysql-datastore_aiomysql-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}, + kafka-messagebroker_kafkapython-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-kafkapythonlatest, + kafka-messagebroker_kafkapython-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-kafkapythonnglatest, + memcached-datastore_aiomcache-{py39,py310,py311,py312,py313,py314,py314t}, + memcached-datastore_bmemcached-{py39,py310,py311,py312,py313,py314,py314t}, + memcached-datastore_memcache-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-memcached01, + memcached-datastore_pylibmc-{py39,py310,py311}, + memcached-datastore_pymemcache-{py39,py310,py311,py312,py313,py314,py314t,pypy311}, + mongodb8-datastore_motor-{py39,py310,py311,py312,py313,py314,py314t}-motorlatest, + mongodb3-datastore_pymongo-{py39,py310,py311,py312}-pymongo03, + mongodb8-datastore_pymongo-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-pymongo04, + mysql-datastore_aiomysql-{py39,py310,py311,py312,py313,py314,py314t,pypy311}, mssql-datastore_pymssql-pymssqllatest-{py39,py310,py311,py312,py313,py314,py314t}, - mssql-datastore_pymssql-pymssql020301-py38, - mysql-datastore_mysql-mysqllatest-{py38,py39,py310,py311,py312,py313,py314,py314t}, - mysql-datastore_mysqldb-{py38,py39,py310,py311,py312,py313,py314,py314t}, - mysql-datastore_pymysql-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}, + mysql-datastore_mysql-mysqllatest-{py39,py310,py311,py312,py313,py314,py314t}, + mysql-datastore_mysqldb-{py39,py310,py311,py312,py313,py314,py314t}, + mysql-datastore_pymysql-{py39,py310,py311,py312,py313,py314,py314t,pypy311}, oracledb-datastore_oracledb-{py39,py310,py311,py312,py313,py314,py314t}-oracledblatest, oracledb-datastore_oracledb-{py39,py313,py314,py314t}-oracledb02, oracledb-datastore_oracledb-{py39,py312}-oracledb01, - nginx-external_httpx-{py38,py39,py310,py311,py312,py313,py314,py314t}, - postgres16-datastore_asyncpg-{py38,py39,py310,py311,py312,py313,py314,py314t}, - postgres16-datastore_psycopg-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-psycopglatest, + nginx-external_httpx-{py39,py310,py311,py312,py313,py314,py314t}, + postgres16-datastore_asyncpg-{py39,py310,py311,py312,py313,py314,py314t}, + postgres16-datastore_psycopg-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-psycopglatest, postgres16-datastore_psycopg-py312-psycopg_{purepython,binary,compiled}0301, - 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,py314,py314t}-pyodbclatest, - postgres9-datastore_postgresql-{py38,py39,py310,py311,py312,py313,py314,py314t}, - python-adapter_asgiref-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-asgireflatest, + postgres16-datastore_psycopg2-{py39,py310,py311,py312}-psycopg2latest, + postgres16-datastore_psycopg2cffi-{py39,py310,py311,py312}-psycopg2cffilatest, + postgres16-datastore_pyodbc-{py39,py310,py311,py312,py313,py314,py314t}-pyodbclatest, + postgres9-datastore_postgresql-{py39,py310,py311,py312,py313,py314,py314t}, + python-adapter_asgiref-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-asgireflatest, python-adapter_asgiref-py310-asgiref{0303,0304,0305,0306,0307}, - python-adapter_cheroot-{py38,py39,py310,py311,py312,py313,py314,py314t}, - python-adapter_daphne-{py38,py39,py310,py311,py312,py313,py314,py314t}-daphnelatest, - python-adapter_gevent-{py38,py310,py311,py312,py313,py314,py314t}, - python-adapter_gunicorn-{py38,py39,py310,py311,py312,py313}-aiohttp03-gunicornlatest, + python-adapter_cheroot-{py39,py310,py311,py312,py313,py314,py314t}, + python-adapter_daphne-{py39,py310,py311,py312,py313,py314,py314t}-daphnelatest, + python-adapter_gevent-{py310,py311,py312,py313,py314,py314t}, + python-adapter_gunicorn-{py39,py310,py311,py312,py313}-aiohttp03-gunicornlatest, ;; Package not ready for Python 3.14 (aiohttp's worker not updated) ; python-adapter_gunicorn-{py314,py314t}-aiohttp03-gunicornlatest, - python-adapter_hypercorn-{py38,py39,py310,py311,py312,py313,py314,py314t}-hypercornlatest, - python-adapter_hypercorn-{py38,py39}-hypercorn{0010,0011,0012,0013}, + python-adapter_hypercorn-{py39,py310,py311,py312,py313,py314,py314t}-hypercornlatest, python-adapter_mcp-{py310,py311,py312,py313,py314,py314t}, - python-adapter_uvicorn-{py38,py39,py310,py311,py312,py313,py314,py314t}-uvicornlatest, - python-adapter_uvicorn-py38-uvicorn020, - python-adapter_waitress-{py38,py39,py310,py311,py312,py313,py314,py314t}-waitresslatest, - python-application_celery-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-celerylatest, + python-adapter_uvicorn-{py39,py310,py311,py312,py313,py314,py314t}-uvicornlatest, + python-adapter_waitress-{py39,py310,py311,py312,py313,py314,py314t}-waitresslatest, + python-application_celery-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-celerylatest, python-application_celery-py311-celery{0504,0503,0502}, - python-component_djangorestframework-{py38,py39,py310,py311,py312,py313,py314,py314t}-djangorestframeworklatest, - python-component_flask_rest-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-flaskrestxlatest, - python-component_graphqlserver-{py38,py39,py310,py311,py312}, + python-component_djangorestframework-{py39,py310,py311,py312,py313,py314,py314t}-djangorestframeworklatest, + python-component_flask_rest-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-flaskrestxlatest, + python-component_graphqlserver-{py39,py310,py311,py312}, ;; Tests need to be updated to support newer graphql-server/sanic versions ; python-component_graphqlserver-{py313,py314,py314t}, - python-component_tastypie-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-tastypielatest, - python-coroutines_asyncio-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}, - python-datastore_sqlite-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}, - python-external_aiobotocore-{py38,py39,py310,py311,py312,py313}-aiobotocorelatest, + python-component_tastypie-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-tastypielatest, + python-coroutines_asyncio-{py39,py310,py311,py312,py313,py314,py314t,pypy311}, + python-datastore_sqlite-{py39,py310,py311,py312,py313,py314,py314t,pypy311}, + python-external_aiobotocore-{py39,py310,py311,py312,py313}-aiobotocorelatest, ;; Package not ready for Python 3.14 (hangs when running) ; python-external_aiobotocore-{py314,py314t}-aiobotocorelatest, - python-external_botocore-{py38,py39,py310,py311,py312,py313,py314,py314t}-botocorelatest, + python-external_botocore-{py39,py310,py311,py312,py313,py314,py314t}-botocorelatest, python-external_botocore-{py311}-botocorelatest-langchain, python-external_botocore-py310-botocore0125, python-external_botocore-py311-botocore0128, - python-external_feedparser-{py38,py39,py310,py311,py312,py313,py314,py314t}-feedparser06, - python-external_http-{py38,py39,py310,py311,py312,py313,py314,py314t}, - python-external_httplib-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}, - python-external_httplib2-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}, + python-external_feedparser-{py39,py310,py311,py312,py313,py314,py314t}-feedparser06, + python-external_http-{py39,py310,py311,py312,py313,py314,py314t}, + python-external_httplib-{py39,py310,py311,py312,py313,py314,py314t,pypy311}, + python-external_httplib2-{py39,py310,py311,py312,py313,py314,py314t,pypy311}, # pyzeebe requires grpcio which does not support pypy python-external_pyzeebe-{py39,py310,py311,py312,py313,py314,py314t}, - python-external_requests-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}, - python-external_urllib3-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-urllib3latest, + python-external_requests-{py39,py310,py311,py312,py313,py314,py314t,pypy311}, + python-external_urllib3-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-urllib3latest, python-external_urllib3-{py312,py313,py314,py314t,pypy311}-urllib30126, - python-framework_aiohttp-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-aiohttp03, - python-framework_ariadne-{py38,py39,py310,py311,py312,py313,py314,py314t}-ariadnelatest, + python-framework_aiohttp-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-aiohttp03, + python-framework_ariadne-{py39,py310,py311,py312,py313,py314,py314t}-ariadnelatest, python-framework_azurefunctions-{py39,py310,py311,py312}, - python-framework_bottle-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-bottle0012, - python-framework_cherrypy-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-CherryPylatest, - python-framework_django-{py38,py39,py310,py311,py312,py313,py314,py314t}-Djangolatest, + python-framework_bottle-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-bottle0012, + python-framework_cherrypy-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-CherryPylatest, + python-framework_django-{py39,py310,py311,py312,py313,py314,py314t}-Djangolatest, python-framework_django-py39-Django{0202,0300,0301,0302,0401}, python-framework_falcon-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-falconlatest, - python-framework_falcon-py38-falcon0410, python-framework_falcon-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-falconmaster, - python-framework_fastapi-{py38,py39,py310,py311,py312,py313,py314,py314t}, - python-framework_flask-{py38,py39,py310,py311,py312,pypy311}-flask02, - ; python-framework_flask-py38-flaskmaster fails, even with Flask-Compress<1.16 and coverage==7.61 for py38 - python-framework_flask-py38-flasklatest, + python-framework_fastapi-{py39,py310,py311,py312,py313,py314,py314t}, + python-framework_flask-{py39,py310,py311,py312,pypy311}-flask02, ; flaskmaster tests disabled until they can be fixed python-framework_flask-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-flask{latest}, - python-framework_graphene-{py38,py39,py310,py311,py312,py313,py314,py314t}-graphenelatest, - python-component_graphenedjango-{py38,py39,py310,py311,py312,py313,py314,py314t}-graphenedjangolatest, - python-framework_graphql-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-graphql03, - python-framework_graphql-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-graphqllatest, - python-framework_pyramid-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-Pyramidlatest, - python-framework_pyramid-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-cornicelatest, - python-framework_sanic-py38-sanic2406, + python-framework_graphene-{py39,py310,py311,py312,py313,py314,py314t}-graphenelatest, + python-component_graphenedjango-{py39,py310,py311,py312,py313,py314,py314t}-graphenedjangolatest, + python-framework_graphql-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-graphql03, + python-framework_graphql-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-graphqllatest, + python-framework_pyramid-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-Pyramidlatest, + python-framework_pyramid-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-cornicelatest, + python-framework_sanic-py311-sanic2406, python-framework_sanic-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-saniclatest, - python-framework_sanic-py38-sanic2290, python-framework_starlette-{py310,pypy311}-starlette{0014,0015,0019,0028}, - python-framework_starlette-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-starlettelatest, - python-framework_starlette-{py38}-starlette002001, - python-framework_strawberry-{py38,py39,py310,py311,py312}-strawberry02352, - python-framework_strawberry-{py38,py39,py310,py311,py312,py313,py314,py314t}-strawberrylatest, - python-framework_tornado-{py38,py39,py310,py311,py312,py313,py314,py314t}-tornadolatest, + python-framework_starlette-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-starlettelatest, + python-framework_strawberry-{py39,py310,py311,py312}-strawberry02352, + python-framework_strawberry-{py39,py310,py311,py312,py313,py314,py314t}-strawberrylatest, + python-framework_tornado-{py39,py310,py311,py312,py313,py314,py314t}-tornadolatest, ; Remove `python-framework_tornado-{py314,py314t}-tornadomaster` temporarily python-framework_tornado-{py310,py311,py312,py313}-tornadomaster, - python-logger_logging-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}, - python-logger_loguru-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-logurulatest, - python-logger_structlog-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-structloglatest, + python-logger_logging-{py39,py310,py311,py312,py313,py314,py314t,pypy311}, + python-logger_loguru-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-logurulatest, + python-logger_structlog-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-structloglatest, python-mlmodel_autogen-{py310,py311,py312,py313,py314,py314t,pypy311}-autogen061, python-mlmodel_autogen-{py310,py311,py312,py313,py314,py314t,pypy311}-autogenlatest, python-mlmodel_strands-{py310,py311,py312,py313}-strandslatest, @@ -188,31 +179,29 @@ envlist = python-mlmodel_langchain-{py39,py310,py311,py312,py313}, ;; Package not ready for Python 3.14 (type annotations not updated) ; python-mlmodel_langchain-{py314,py314t}, - python-mlmodel_openai-openai0-{py38,py39,py310,py311,py312}, + python-mlmodel_openai-openai0-{py39,py310,py311,py312}, python-mlmodel_openai-openai107-py312, - python-mlmodel_openai-openailatest-{py38,py39,py310,py311,py312,py313,py314,py314t}, - python-mlmodel_sklearn-{py38,py39,py310,py311,py312,py313,py314,py314t}-scikitlearnlatest, - python-template_genshi-{py38,py39,py310,py311,py312,py313,py314,py314t}-genshilatest, - python-template_jinja2-{py38,py39,py310,py311,py312,py313,py314,py314t}-jinja2latest, - python-template_mako-{py38,py39,py310,py311,py312,py313,py314,py314t}, - rabbitmq-messagebroker_pika-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-pikalatest, - rabbitmq-messagebroker_kombu-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-kombulatest, - rabbitmq-messagebroker_kombu-{py38,py39,py310,pypy311}-kombu050204, - redis-datastore_redis-{py38,py39,py310,py311,pypy311}-redis04, - redis-datastore_redis-{py38,py39,py310,py311,py312,pypy311}-redis05, - redis-datastore_redis-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-redislatest, + python-mlmodel_openai-openailatest-{py39,py310,py311,py312,py313,py314,py314t}, + python-mlmodel_sklearn-{py39,py310,py311,py312,py313,py314,py314t}-scikitlearnlatest, + python-template_genshi-{py39,py310,py311,py312,py313,py314,py314t}-genshilatest, + python-template_jinja2-{py39,py310,py311,py312,py313,py314,py314t}-jinja2latest, + python-template_mako-{py39,py310,py311,py312,py313,py314,py314t}, + rabbitmq-messagebroker_pika-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-pikalatest, + rabbitmq-messagebroker_kombu-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-kombulatest, + rabbitmq-messagebroker_kombu-{py39,py310,pypy311}-kombu050204, + redis-datastore_redis-{py39,py310,py311,pypy311}-redis04, + redis-datastore_redis-{py39,py310,py311,py312,pypy311}-redis05, + redis-datastore_redis-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-redislatest, rediscluster-datastore_rediscluster-{py312,py313,py314,py314t,pypy311}-redislatest, - valkey-datastore_valkey-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}-valkeylatest, - solr-datastore_pysolr-{py38,py39,py310,py311,py312,py313,py314,py314t,pypy311}, + valkey-datastore_valkey-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-valkeylatest, + solr-datastore_pysolr-{py39,py310,py311,py312,py313,py314,py314t,pypy311}, [testenv] deps = # Base Dependencies - {py39,py310,py311,py312,py313,py314,py314t,pypy311}: pytest==8.4.1 - py38: pytest==8.3.5 - {py39,py310,py311,py312,py313,py314,py314t,pypy311}: WebTest==3.0.6 - py38: WebTest==3.0.1 - py313,py314,py314t: legacy-cgi==2.6.1 # cgi was removed from the stdlib in 3.13, and is required for WebTest + {py310,py311,py312,py313,py314,py314t,pypy311}: pytest==9.0.2 + py39: pytest==8.4.2 + WebTest==3.0.7 iniconfig coverage @@ -234,14 +223,8 @@ deps = adapter_gunicorn-gunicorn19: gunicorn<20 adapter_gunicorn-gunicornlatest: gunicorn adapter_hypercorn-hypercornlatest: hypercorn[h3]!=0.18 - adapter_hypercorn-hypercorn0013: hypercorn[h3]<0.14 - adapter_hypercorn-hypercorn0012: hypercorn[h3]<0.13 - adapter_hypercorn-hypercorn0011: hypercorn[h3]<0.12 - adapter_hypercorn-hypercorn0010: hypercorn[h3]<0.11 adapter_hypercorn: niquests adapter_mcp: fastmcp - adapter_uvicorn-uvicorn020: uvicorn<0.21 - adapter_uvicorn-uvicorn020: uvloop<0.20 adapter_uvicorn-uvicornlatest: uvicorn adapter_uvicorn: typing-extensions adapter_uvicorn: uvloop @@ -280,7 +263,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-{py38,py39,py310,py311,py312,py313,py314,py314t}: uvloop + coroutines_asyncio-{py39,py310,py311,py312,py313,py314,py314t}: uvloop cross_agent: requests datastore_asyncpg: asyncpg datastore_aiomcache: aiomcache @@ -289,7 +272,6 @@ deps = datastore_aiomysql: sqlalchemy<2 datastore_bmemcached: python-binary-memcached datastore_cassandradriver-cassandralatest: cassandra-driver - datastore_cassandradriver-cassandra032903: cassandra-driver<3.29.3 datastore_cassandradriver: twisted datastore_elasticsearch: requests datastore_elasticsearch: httpx @@ -320,7 +302,6 @@ deps = datastore_pymongo-pymongo03: pymongo<4.0 datastore_pymongo-pymongo04: pymongo<5.0 datastore_pymssql-pymssqllatest: pymssql - datastore_pymssql-pymssql020301: pymssql==2.3.1 datastore_pymysql: PyMySQL datastore_pymysql: cryptography datastore_pysolr: pysolr<4.0 @@ -369,7 +350,6 @@ deps = framework_django-Django0401: Django<4.2 framework_django-Djangolatest: Django framework_django-Djangomaster: https://github.com/django/django/archive/main.zip - framework_falcon-falcon0410: falcon<4.2 framework_falcon-falconlatest: falcon framework_falcon-falconmaster: https://github.com/falconry/falcon/archive/master.zip framework_fastapi: fastapi @@ -396,12 +376,8 @@ deps = framework_pyramid: routes framework_pyramid-cornicelatest: cornice framework_pyramid-Pyramidlatest: Pyramid - framework_sanic-sanic2290: sanic<22.9.1 framework_sanic-sanic2406: sanic<24.07 framework_sanic-saniclatest: sanic - ; This is the last version of tracerite that supports Python 3.8 - framework_sanic-sanic{2290,2406}: tracerite<1.1.2 - framework_sanic-sanic2290: websockets<11 ; For test_exception_in_middleware test, anyio is used: ; https://github.com/encode/starlette/pull/1157 ; but anyiolatest creates breaking changes to our tests @@ -411,7 +387,6 @@ deps = framework_starlette-starlette0014: starlette<0.15 framework_starlette-starlette0015: starlette<0.16 framework_starlette-starlette0019: starlette<0.20 - framework_starlette-starlette002001: starlette==0.20.1 framework_starlette-starlette0028: starlette<0.29 framework_starlette-starlettelatest: starlette<0.35 framework_strawberry: starlette