From b55d158d652629db51d19501d543dad591f2046e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20K=C5=82oczko?= <31284574+kloczek@users.noreply.github.com> Date: Wed, 31 Jul 2024 11:12:14 +0100 Subject: [PATCH] Drop Python 3.7 support (#2561) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomasz Kłoczko Co-authored-by: Quentin Pradet (cherry picked from commit dec4858514354b5dcef54a9f19e2f5bed9abff49) --- .buildkite/pipeline.yml | 3 +-- .github/workflows/ci.yml | 2 +- docs/guide/getting-started.asciidoc | 2 +- docs/sphinx/conf.py | 1 - docs/sphinx/quickstart.rst | 2 +- elasticsearch/_otel.py | 8 ++------ examples/bulk-ingest/bulk-ingest.py | 2 +- noxfile.py | 4 ++-- pyproject.toml | 5 +---- .../test_async/test_server/test_clients.py | 2 -- .../test_async/test_server/test_helpers.py | 18 +++++++++--------- .../test_server/test_rest_api_spec.py | 4 ++-- .../test_async/test_transport.py | 4 +--- test_elasticsearch/test_client/test_options.py | 1 - .../test_client/test_overrides.py | 1 - test_elasticsearch/test_client/test_utils.py | 2 -- test_elasticsearch/test_helpers.py | 3 +-- test_elasticsearch/test_serializer.py | 1 - test_elasticsearch/test_server/test_clients.py | 1 - test_elasticsearch/test_server/test_helpers.py | 6 +++--- .../test_server/test_rest_api_spec.py | 12 ++++-------- test_elasticsearch/test_transport.py | 3 +-- utils/build-dists.py | 2 +- utils/bump-version.py | 2 +- utils/license-headers.py | 4 ++-- 25 files changed, 35 insertions(+), 60 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index f5e48c9eb..ff911719e 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -11,7 +11,6 @@ steps: matrix: setup: python: - - "3.7" - "3.8" - "3.9" - "3.10" @@ -24,7 +23,7 @@ steps: - "test" adjustments: - with: - python: "3.7" + python: "3.8" connection: "urllib3" nox_session: "test_otel" - with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b662f866..94c554900 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] nox-session: [""] runs-on: ["ubuntu-latest"] diff --git a/docs/guide/getting-started.asciidoc b/docs/guide/getting-started.asciidoc index db0a8095b..1b964e50c 100644 --- a/docs/guide/getting-started.asciidoc +++ b/docs/guide/getting-started.asciidoc @@ -8,7 +8,7 @@ operations with it. [discrete] === Requirements -* https://www.python.org/[Python] 3.7 or newer +* https://www.python.org/[Python] 3.8 or newer * https://pip.pypa.io/en/stable/[`pip`], installed by default alongside Python [discrete] diff --git a/docs/sphinx/conf.py b/docs/sphinx/conf.py index e6fe394ec..d7c3f7751 100644 --- a/docs/sphinx/conf.py +++ b/docs/sphinx/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Licensed to Elasticsearch B.V. under one or more contributor # license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright diff --git a/docs/sphinx/quickstart.rst b/docs/sphinx/quickstart.rst index 51938e921..563ea6f23 100644 --- a/docs/sphinx/quickstart.rst +++ b/docs/sphinx/quickstart.rst @@ -9,7 +9,7 @@ operations like indexing or searching documents. Requirements ------------ -- `Python `_ 3.7 or newer +- `Python `_ 3.8 or newer - `pip `_ diff --git a/elasticsearch/_otel.py b/elasticsearch/_otel.py index 9264569b3..039f7798b 100644 --- a/elasticsearch/_otel.py +++ b/elasticsearch/_otel.py @@ -19,10 +19,7 @@ import contextlib import os -from typing import TYPE_CHECKING, Generator, Mapping - -if TYPE_CHECKING: - from typing import Literal +from typing import Generator, Literal, Mapping try: from opentelemetry import trace @@ -48,8 +45,7 @@ def __init__( self, enabled: bool | None = None, tracer: trace.Tracer | None = None, - # TODO import Literal at the top-level when dropping Python 3.7 - body_strategy: 'Literal["omit", "raw"]' | None = None, + body_strategy: Literal["omit", "raw"] | None = None, ): if enabled is None: enabled = os.environ.get(ENABLED_ENV_VAR, "true") == "true" diff --git a/examples/bulk-ingest/bulk-ingest.py b/examples/bulk-ingest/bulk-ingest.py index 4c5c34c86..e1619ecde 100644 --- a/examples/bulk-ingest/bulk-ingest.py +++ b/examples/bulk-ingest/bulk-ingest.py @@ -66,7 +66,7 @@ def generate_actions(): yields a single document. This function is passed into the bulk() helper to create many documents in sequence. """ - with open(DATASET_PATH, mode="r") as f: + with open(DATASET_PATH) as f: reader = csv.DictReader(f) for row in reader: diff --git a/noxfile.py b/noxfile.py index 2c97b5679..69e53417f 100644 --- a/noxfile.py +++ b/noxfile.py @@ -45,14 +45,14 @@ def pytest_argv(): ] -@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]) +@nox.session(python=["3.8", "3.9", "3.10", "3.11", "3.12"]) def test(session): session.install(".[dev]", env=INSTALL_ENV, silent=False) session.run(*pytest_argv()) -@nox.session(python=["3.7", "3.12"]) +@nox.session(python=["3.8", "3.12"]) def test_otel(session): session.install( ".[dev]", diff --git a/pyproject.toml b/pyproject.toml index 20f206d27..2a35c51f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "elasticsearch" description = "Python client for Elasticsearch" readme = "README.md" license = "Apache-2.0" -requires-python = ">=3.7" +requires-python = ">=3.8" authors = [ { name = "Elastic Client Library Maintainers", email = "client-libs@elastic.co" }, ] @@ -21,7 +21,6 @@ classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", @@ -72,8 +71,6 @@ dev = [ "simsimd", "pandas", "mapbox-vector-tile", - # Python 3.7 gets an old version of mapbox-vector-tile, requiring an old version of protobuf - "protobuf<4; python_version<=\"3.7\"", ] docs = [ "sphinx", diff --git a/test_elasticsearch/test_async/test_server/test_clients.py b/test_elasticsearch/test_async/test_server/test_clients.py index 8ae5726c1..00de2c7fb 100644 --- a/test_elasticsearch/test_async/test_server/test_clients.py +++ b/test_elasticsearch/test_async/test_server/test_clients.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Licensed to Elasticsearch B.V. under one or more contributor # license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright @@ -16,7 +15,6 @@ # specific language governing permissions and limitations # under the License. -from __future__ import unicode_literals import pytest diff --git a/test_elasticsearch/test_async/test_server/test_helpers.py b/test_elasticsearch/test_async/test_server/test_helpers.py index 6e9de3075..746dc1028 100644 --- a/test_elasticsearch/test_async/test_server/test_helpers.py +++ b/test_elasticsearch/test_async/test_server/test_helpers.py @@ -33,13 +33,13 @@ class AsyncMock(MagicMock): async def __call__(self, *args, **kwargs): - return super(AsyncMock, self).__call__(*args, **kwargs) + return super().__call__(*args, **kwargs) def __await__(self): return self().__await__() -class FailingBulkClient(object): +class FailingBulkClient: def __init__( self, client, @@ -68,7 +68,7 @@ def options(self, **_): return self -class TestStreamingBulk(object): +class TestStreamingBulk: async def test_actions_remain_unchanged(self, async_client): actions = [{"_id": 1}, {"_id": 2}] async for ok, item in helpers.async_streaming_bulk( @@ -294,7 +294,7 @@ async def streaming_bulk(): assert 4 == failing_client._called -class TestBulk(object): +class TestBulk: async def test_bulk_works_with_single_item(self, async_client): docs = [{"answer": 42, "_id": 1}] success, failed = await helpers.async_bulk( @@ -458,7 +458,7 @@ async def scan_teardown(async_client): await async_client.clear_scroll(scroll_id="_all") -class TestScan(object): +class TestScan: async def test_order_can_be_preserved(self, async_client, scan_teardown): bulk = [] for x in range(100): @@ -493,8 +493,8 @@ async def test_all_documents_are_read(self, async_client, scan_teardown): ] assert 100 == len(docs) - assert set(map(str, range(100))) == set(d["_id"] for d in docs) - assert set(range(100)) == set(d["_source"]["answer"] for d in docs) + assert set(map(str, range(100))) == {d["_id"] for d in docs} + assert set(range(100)) == {d["_source"]["answer"] for d in docs} async def test_scroll_error(self, async_client, scan_teardown): bulk = [] @@ -881,7 +881,7 @@ async def reindex_setup(async_client): yield -class TestReindex(object): +class TestReindex: async def test_reindex_passes_kwargs_to_scan_and_bulk( self, async_client, reindex_setup ): @@ -1031,7 +1031,7 @@ async def reindex_data_stream_setup(async_client): yield -class TestAsyncDataStreamReindex(object): +class TestAsyncDataStreamReindex: @pytest.mark.parametrize("op_type", [None, "create"]) async def test_reindex_index_datastream( self, op_type, async_client, reindex_data_stream_setup diff --git a/test_elasticsearch/test_async/test_server/test_rest_api_spec.py b/test_elasticsearch/test_async/test_server/test_rest_api_spec.py index fd4fd04e3..eee2364f6 100644 --- a/test_elasticsearch/test_async/test_server/test_rest_api_spec.py +++ b/test_elasticsearch/test_async/test_server/test_rest_api_spec.py @@ -228,9 +228,9 @@ async def _feature_enabled(self, name): if XPACK_FEATURES is None: try: xinfo = await self.client.xpack.info() - XPACK_FEATURES = set( + XPACK_FEATURES = { f for f in xinfo["features"] if xinfo["features"][f]["enabled"] - ) + } IMPLEMENTED_FEATURES.add("xpack") except RequestError: XPACK_FEATURES = set() diff --git a/test_elasticsearch/test_async/test_transport.py b/test_elasticsearch/test_async/test_transport.py index 918e19e57..76a71f50b 100644 --- a/test_elasticsearch/test_async/test_transport.py +++ b/test_elasticsearch/test_async/test_transport.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Licensed to Elasticsearch B.V. under one or more contributor # license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright @@ -16,7 +15,6 @@ # specific language governing permissions and limitations # under the License. -from __future__ import unicode_literals import asyncio import re @@ -280,7 +278,7 @@ def test_kwargs_passed_on_to_node_pool(self): ) assert dt is client.transport.node_pool.dead_node_backoff_factor - class MyConnection(object): + class MyConnection: def __init__(self, *_, **__): pass diff --git a/test_elasticsearch/test_client/test_options.py b/test_elasticsearch/test_client/test_options.py index adf7a1d0d..b7fa3cfda 100644 --- a/test_elasticsearch/test_client/test_options.py +++ b/test_elasticsearch/test_client/test_options.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Licensed to Elasticsearch B.V. under one or more contributor # license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright diff --git a/test_elasticsearch/test_client/test_overrides.py b/test_elasticsearch/test_client/test_overrides.py index fd8ad9f65..28fa8708b 100644 --- a/test_elasticsearch/test_client/test_overrides.py +++ b/test_elasticsearch/test_client/test_overrides.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Licensed to Elasticsearch B.V. under one or more contributor # license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright diff --git a/test_elasticsearch/test_client/test_utils.py b/test_elasticsearch/test_client/test_utils.py index 3c245f397..e53145bfd 100644 --- a/test_elasticsearch/test_client/test_utils.py +++ b/test_elasticsearch/test_client/test_utils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Licensed to Elasticsearch B.V. under one or more contributor # license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright @@ -16,7 +15,6 @@ # specific language governing permissions and limitations # under the License. -from __future__ import unicode_literals from elasticsearch._sync.client.utils import _quote diff --git a/test_elasticsearch/test_helpers.py b/test_elasticsearch/test_helpers.py index a8efb151c..c9284afc5 100644 --- a/test_elasticsearch/test_helpers.py +++ b/test_elasticsearch/test_helpers.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Licensed to Elasticsearch B.V. under one or more contributor # license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright @@ -75,7 +74,7 @@ def test_chunk_sent_from_different_threads(self, _process_bulk_chunk): chunk_size=2, ) ) - assert len(set([r[1] for r in results])) > 1 + assert len({r[1] for r in results}) > 1 class TestChunkActions: diff --git a/test_elasticsearch/test_serializer.py b/test_elasticsearch/test_serializer.py index 4f66ba9a2..ba5f1adec 100644 --- a/test_elasticsearch/test_serializer.py +++ b/test_elasticsearch/test_serializer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Licensed to Elasticsearch B.V. under one or more contributor # license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright diff --git a/test_elasticsearch/test_server/test_clients.py b/test_elasticsearch/test_server/test_clients.py index e7c2a78e6..93720ed14 100644 --- a/test_elasticsearch/test_server/test_clients.py +++ b/test_elasticsearch/test_server/test_clients.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Licensed to Elasticsearch B.V. under one or more contributor # license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright diff --git a/test_elasticsearch/test_server/test_helpers.py b/test_elasticsearch/test_server/test_helpers.py index 2978e20d9..33c31c364 100644 --- a/test_elasticsearch/test_server/test_helpers.py +++ b/test_elasticsearch/test_server/test_helpers.py @@ -27,7 +27,7 @@ from elasticsearch.helpers import ScanError -class FailingBulkClient(object): +class FailingBulkClient: def __init__( self, client, @@ -463,8 +463,8 @@ def test_all_documents_are_read(sync_client): docs = list(helpers.scan(sync_client, index="test_index", size=2)) assert 100 == len(docs) - assert set(map(str, range(100))) == set(d["_id"] for d in docs) - assert set(range(100)) == set(d["_source"]["answer"] for d in docs) + assert set(map(str, range(100))) == {d["_id"] for d in docs} + assert set(range(100)) == {d["_source"]["answer"] for d in docs} @pytest.mark.usefixtures("scan_teardown") diff --git a/test_elasticsearch/test_server/test_rest_api_spec.py b/test_elasticsearch/test_server/test_rest_api_spec.py index e45625842..6ede3b753 100644 --- a/test_elasticsearch/test_server/test_rest_api_spec.py +++ b/test_elasticsearch/test_server/test_rest_api_spec.py @@ -24,7 +24,6 @@ import json import os import re -import sys import warnings import zipfile from typing import Tuple, Union @@ -131,10 +130,7 @@ XPACK_FEATURES = None ES_VERSION = None -RUN_ASYNC_REST_API_TESTS = ( - sys.version_info >= (3, 8) - and os.environ.get("PYTHON_CONNECTION_CLASS") == "requests" -) +RUN_ASYNC_REST_API_TESTS = os.environ.get("PYTHON_CONNECTION_CLASS") == "requests" FALSEY_VALUES = ("", None, False, 0, 0.0) @@ -456,7 +452,7 @@ def _resolve(self, value): if isinstance(value, string_types): value = value.strip() elif isinstance(value, dict): - value = dict((k, self._resolve(v)) for (k, v) in value.items()) + value = {k: self._resolve(v) for (k, v) in value.items()} elif isinstance(value, list): value = list(map(self._resolve, value)) return value @@ -495,9 +491,9 @@ def _feature_enabled(self, name): if XPACK_FEATURES is None: try: xinfo = self.client.xpack.info() - XPACK_FEATURES = set( + XPACK_FEATURES = { f for f in xinfo["features"] if xinfo["features"][f]["enabled"] - ) + } IMPLEMENTED_FEATURES.add("xpack") except RequestError: XPACK_FEATURES = set() diff --git a/test_elasticsearch/test_transport.py b/test_elasticsearch/test_transport.py index ce8b7f901..5161cd8e1 100644 --- a/test_elasticsearch/test_transport.py +++ b/test_elasticsearch/test_transport.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Licensed to Elasticsearch B.V. under one or more contributor # license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright @@ -311,7 +310,7 @@ def test_kwargs_passed_on_to_node_pool(self): assert dt is client.transport.node_pool.dead_node_backoff_factor def test_custom_node_class(self): - class MyConnection(object): + class MyConnection: def __init__(self, *_, **__): pass diff --git a/utils/build-dists.py b/utils/build-dists.py index 103e95f0e..15187c5a9 100644 --- a/utils/build-dists.py +++ b/utils/build-dists.py @@ -50,7 +50,7 @@ def run(*argv, expect_exit_code=0): else: os.chdir(tmp_dir) - cmd = " ".join(shlex.quote(x) for x in argv) + cmd = shlex.join(argv) print("$ " + cmd) exit_code = os.system(cmd) if exit_code != expect_exit_code: diff --git a/utils/bump-version.py b/utils/bump-version.py index 821d92344..07507ea48 100644 --- a/utils/bump-version.py +++ b/utils/bump-version.py @@ -29,7 +29,7 @@ def find_and_replace(path, pattern, replace): # Does a find and replace within a file path and complains # if the given pattern isn't found in the file. - with open(path, "r") as f: + with open(path) as f: old_data = f.read() if re.search(pattern, old_data, flags=re.MULTILINE) is None: diff --git a/utils/license-headers.py b/utils/license-headers.py index b4fc7432b..4b7b978c5 100644 --- a/utils/license-headers.py +++ b/utils/license-headers.py @@ -66,7 +66,7 @@ def find_files_to_fix(sources: List[str]) -> Iterator[str]: def does_file_need_fix(filepath: str) -> bool: if not re.search(r"\.pyi?$", filepath): return False - with open(filepath, mode="r") as f: + with open(filepath) as f: first_license_line = None for line in f: if line == license_header_lines[0]: @@ -83,7 +83,7 @@ def does_file_need_fix(filepath: str) -> bool: def add_header_to_file(filepath: str) -> None: - with open(filepath, mode="r") as f: + with open(filepath) as f: lines = list(f) i = 0 for i, line in enumerate(lines):