diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 60df28286..8948dfa4d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ '3.9', '3.10', '3.11', '3.12' ] + python-version: [ '3.10', '3.11', '3.12' ] env: PYTEST_ADDOPTS: >- --log-dir=/tmp/ci-logs diff --git a/.gitignore b/.gitignore index 59077267f..237f1ddf4 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ src/karapace/version.py .python-version .hypothesis/ .DS_Store +*.coverage.* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1161ba0b7..ee1505771 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,7 +34,7 @@ repos: rev: v3.4.0 hooks: - id: pyupgrade - args: [ --py39-plus ] + args: [ --py310-plus ] - repo: https://github.com/pycqa/autoflake rev: v2.1.1 diff --git a/GNUmakefile b/GNUmakefile index 032def928..646a4e271 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -3,7 +3,7 @@ SHELL := /usr/bin/env bash VENV_DIR ?= $(CURDIR)/venv PIP ?= pip3 --disable-pip-version-check --no-input --require-virtualenv PYTHON ?= python3 -PYTHON_VERSION ?= 3.9 +PYTHON_VERSION ?= 3.10 DOCKER_COMPOSE ?= docker compose KARAPACE-CLI ?= $(DOCKER_COMPOSE) -f container/compose.yml run --rm karapace-cli @@ -108,8 +108,8 @@ pin-requirements: .PHONY: start-karapace-docker-resources start-karapace-docker-resources: export KARAPACE_VERSION ?= 4.1.1.dev44+gac20eeed.d20241205 start-karapace-docker-resources: - sudo touch .coverage.3.9 .coverage.3.10 .coverage.3.11 .coverage.3.12 - sudo chown ${RUNNER_UID}:${RUNNER_GID} .coverage.3.9 .coverage.3.10 .coverage.3.11 .coverage.3.12 + sudo touch .coverage.3.10 .coverage.3.11 .coverage.3.12 + sudo chown ${RUNNER_UID}:${RUNNER_GID} .coverage.3.10 .coverage.3.11 .coverage.3.12 $(DOCKER_COMPOSE) -f container/compose.yml up -d --build --wait --detach .PHONY: unit-tests-in-docker diff --git a/container/Dockerfile.dev b/container/Dockerfile.dev index 37a6cb5ab..3ad6e74b3 100644 --- a/container/Dockerfile.dev +++ b/container/Dockerfile.dev @@ -7,7 +7,7 @@ ARG RUNNER_GID # Setup files and directories. RUN mkdir /opt/karapace /opt/karapace/runtime /var/log/karapace /opt/karapace/coverage \ -&& touch /opt/karapace/coverage/.coverage.3.9 /opt/karapace/coverage/.coverage.3.10 /opt/karapace/coverage/.coverage.3.11 /opt/karapace/coverage/.coverage.3.12 \ +&& touch /opt/karapace/coverage/.coverage.3.10 /opt/karapace/coverage/.coverage.3.11 /opt/karapace/coverage/.coverage.3.12 \ && chown --recursive "$RUNNER_UID:$RUNNER_GID" /opt/karapace /opt/karapace/coverage /var/log/karapace # Create, activate, and enforce usage of virtualenv. diff --git a/container/compose.yml b/container/compose.yml index 4b7d8728b..5fa01f270 100644 --- a/container/compose.yml +++ b/container/compose.yml @@ -140,7 +140,6 @@ services: - ../.pre-commit-config.yaml:/opt/karapace/.pre-commit-config.yaml - ../.pylintrc:/opt/karapace/.pylintrc - ../.coveragerc:/opt/karapace/.coveragerc - - ../.coverage.3.9:/opt/karapace/coverage/.coverage.3.9 - ../.coverage.3.10:/opt/karapace/coverage/.coverage.3.10 - ../.coverage.3.11:/opt/karapace/coverage/.coverage.3.11 - ../.coverage.3.12:/opt/karapace/coverage/.coverage.3.12 diff --git a/mypy.ini b/mypy.ini index 4c8aacaca..523883eb5 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,6 +1,6 @@ [mypy] mypy_path = $MYPY_CONFIG_FILE_DIR/stubs -python_version = 3.9 +python_version = 3.10 packages = karapace show_error_codes = True pretty = True diff --git a/pyproject.toml b/pyproject.toml index 9df5adf5c..df9a33bda 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "karapace" -requires-python = ">= 3.9" +requires-python = ">= 3.10" dynamic = ["version"] readme = "README.rst" license = {file = "LICENSE"} @@ -56,7 +56,6 @@ classifiers=[ "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "License :: OSI Approved :: Apache Software License", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", diff --git a/requirements/requirements-dev.txt b/requirements/requirements-dev.txt index bb95b5fc9..00c6d4d4c 100644 --- a/requirements/requirements-dev.txt +++ b/requirements/requirements-dev.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.9 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # # make pin-requirements @@ -119,9 +119,9 @@ httpcore==1.0.7 # via httpx httptools==0.6.4 # via uvicorn -httpx==0.28.0 +httpx==0.28.1 # via fastapi -hypothesis==6.122.1 +hypothesis==6.122.3 # via karapace (/karapace/pyproject.toml) idna==3.10 # via @@ -131,9 +131,7 @@ idna==3.10 # requests # yarl importlib-metadata==8.5.0 - # via - # flask - # opentelemetry-api + # via opentelemetry-api iniconfig==2.0.0 # via pytest isodate==0.7.2 @@ -166,7 +164,7 @@ multidict==6.1.0 # via # aiohttp # yarl -networkx==3.2.1 +networkx==3.4.2 # via karapace (/karapace/pyproject.toml) opentelemetry-api==1.28.2 # via @@ -314,7 +312,6 @@ typing-extensions==4.12.2 # pydantic # pydantic-core # rich-toolkit - # starlette # typer # uvicorn ujson==5.10.0 diff --git a/requirements/requirements-typing.txt b/requirements/requirements-typing.txt index eec3f3d32..bcda10c84 100644 --- a/requirements/requirements-typing.txt +++ b/requirements/requirements-typing.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.9 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # # make pin-requirements @@ -78,7 +78,7 @@ httpcore==1.0.7 # via httpx httptools==0.6.4 # via uvicorn -httpx==0.28.0 +httpx==0.28.1 # via fastapi idna==3.10 # via @@ -112,7 +112,7 @@ mypy==1.13.0 # via karapace (/karapace/pyproject.toml) mypy-extensions==1.0.0 # via mypy -networkx==3.2.1 +networkx==3.4.2 # via karapace (/karapace/pyproject.toml) opentelemetry-api==1.28.2 # via @@ -211,7 +211,7 @@ typer==0.15.1 # via fastapi-cli types-cachetools==5.5.0.20240820 # via karapace (/karapace/pyproject.toml) -types-jsonschema==4.23.0.20240813 +types-jsonschema==4.23.0.20241208 # via karapace (/karapace/pyproject.toml) types-protobuf==3.20.4.6 # via karapace (/karapace/pyproject.toml) @@ -227,7 +227,6 @@ typing-extensions==4.12.2 # pydantic # pydantic-core # rich-toolkit - # starlette # typer # uvicorn ujson==5.10.0 diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 4a32fdcd3..edf3064a4 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.9 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # # make pin-requirements @@ -77,7 +77,7 @@ httpcore==1.0.7 # via httpx httptools==0.6.4 # via uvicorn -httpx==0.28.0 +httpx==0.28.1 # via fastapi idna==3.10 # via @@ -107,7 +107,7 @@ multidict==6.1.0 # via # aiohttp # yarl -networkx==3.2.1 +networkx==3.4.2 # via karapace (/karapace/pyproject.toml) opentelemetry-api==1.28.2 # via @@ -210,7 +210,6 @@ typing-extensions==4.12.2 # pydantic # pydantic-core # rich-toolkit - # starlette # typer # uvicorn ujson==5.10.0 diff --git a/runtime.txt b/runtime.txt index 57f558859..39298aa92 100644 --- a/runtime.txt +++ b/runtime.txt @@ -1 +1 @@ -python-3.9.20 +python-3.10.16 diff --git a/src/karapace/anonymize_schemas/anonymize_avro.py b/src/karapace/anonymize_schemas/anonymize_avro.py index 69906c94d..a5521464b 100644 --- a/src/karapace/anonymize_schemas/anonymize_avro.py +++ b/src/karapace/anonymize_schemas/anonymize_avro.py @@ -4,8 +4,7 @@ Copyright (c) 2023 Aiven Ltd See LICENSE for details """ -from typing import Any, Union -from typing_extensions import TypeAlias +from typing import Any, TypeAlias, Union import hashlib import re diff --git a/src/karapace/avro_dataclasses/introspect.py b/src/karapace/avro_dataclasses/introspect.py index 7ba38ab00..254a5bc71 100644 --- a/src/karapace/avro_dataclasses/introspect.py +++ b/src/karapace/avro_dataclasses/introspect.py @@ -10,7 +10,8 @@ from dataclasses import Field, fields, is_dataclass, MISSING from enum import Enum from functools import lru_cache -from typing import Final, get_args, get_origin, TYPE_CHECKING, TypeVar, Union +from types import UnionType +from typing import Final, get_args, get_origin, TYPE_CHECKING, TypeVar import datetime import uuid @@ -90,7 +91,7 @@ def _field_type(field: Field, type_: object) -> AvroType: # pylint: disable=too origin = get_origin(type_) # Handle union types. - if origin is Union: + if origin is UnionType: return [_field_type(field, unit) for unit in get_args(type_)] # type: ignore[misc] # Handle array types. diff --git a/src/karapace/avro_dataclasses/models.py b/src/karapace/avro_dataclasses/models.py index 9bcd630cf..46fa477a0 100644 --- a/src/karapace/avro_dataclasses/models.py +++ b/src/karapace/avro_dataclasses/models.py @@ -5,12 +5,13 @@ from __future__ import annotations from .introspect import record_schema -from collections.abc import Iterable, Mapping +from collections.abc import Callable, Iterable, Mapping from dataclasses import asdict, fields, is_dataclass from enum import Enum from functools import lru_cache, partial -from typing import Callable, cast, IO, TYPE_CHECKING, TypeVar, Union -from typing_extensions import get_args, get_origin, Self +from types import UnionType +from typing import cast, get_args, get_origin, IO, TYPE_CHECKING, TypeVar +from typing_extensions import Self import avro import avro.io @@ -100,7 +101,7 @@ def from_avro_value(type_: object) -> Parser | None: # With the avro library we need to manually handle union types. We only support the # special case of nullable types for now. - if origin is Union: + if origin is UnionType: try: a, b = get_args(type_) except ValueError: diff --git a/src/karapace/avro_dataclasses/schema.py b/src/karapace/avro_dataclasses/schema.py index b49c1d2c5..31990b601 100644 --- a/src/karapace/avro_dataclasses/schema.py +++ b/src/karapace/avro_dataclasses/schema.py @@ -5,8 +5,8 @@ from __future__ import annotations from collections.abc import Mapping -from typing import Literal -from typing_extensions import NotRequired, TypeAlias, TypedDict +from typing import Literal, TypeAlias +from typing_extensions import NotRequired, TypedDict Primitive: TypeAlias = Literal["int", "long", "string", "null", "bytes", "boolean"] LogicalType: TypeAlias = Literal["timestamp-millis", "uuid"] diff --git a/src/karapace/backup/api.py b/src/karapace/backup/api.py index 6e3628414..60be1b966 100644 --- a/src/karapace/backup/api.py +++ b/src/karapace/backup/api.py @@ -22,7 +22,7 @@ from .poll_timeout import PollTimeout from .topic_configurations import ConfigSource, get_topic_configurations from aiokafka.errors import KafkaError, TopicAlreadyExistsError -from collections.abc import Iterator, Mapping, Sized +from collections.abc import Callable, Iterator, Mapping, Sized from concurrent.futures import Future from confluent_kafka import Message, TopicPartition from enum import Enum @@ -42,7 +42,7 @@ from pathlib import Path from rich.console import Console from tenacity import retry, retry_if_exception_type, RetryCallState, stop_after_delay, wait_fixed -from typing import Callable, Literal, NewType, TypeVar +from typing import Literal, NewType, TypeVar import contextlib import datetime diff --git a/src/karapace/backup/backends/reader.py b/src/karapace/backup/backends/reader.py index d1d32bfe8..b803e596d 100644 --- a/src/karapace/backup/backends/reader.py +++ b/src/karapace/backup/backends/reader.py @@ -4,12 +4,11 @@ """ from __future__ import annotations -from collections.abc import Generator, Iterator, Mapping, Sequence +from collections.abc import Callable, Generator, Iterator, Mapping, Sequence from karapace.dataclasses import default_dataclass from karapace.typing import JsonData, JsonObject from pathlib import Path -from typing import Callable, ClassVar, Final, IO, Optional, TypeVar, Union -from typing_extensions import TypeAlias +from typing import ClassVar, Final, IO, Optional, TypeAlias, TypeVar, Union import abc diff --git a/src/karapace/backup/backends/v3/backend.py b/src/karapace/backup/backends/v3/backend.py index c2aca1f25..45c9855bc 100644 --- a/src/karapace/backup/backends/v3/backend.py +++ b/src/karapace/backup/backends/v3/backend.py @@ -9,7 +9,7 @@ from .readers import read_metadata, read_records from .schema import ChecksumAlgorithm, DataFile, Header, Metadata, Record from .writers import write_metadata, write_record -from collections.abc import Generator, Iterator, Mapping, Sequence +from collections.abc import Callable, Generator, Iterator, Mapping, Sequence from confluent_kafka import Message from dataclasses import dataclass from karapace.backup.backends.reader import BaseBackupReader, Instruction, ProducerSend, RestoreTopic @@ -19,8 +19,7 @@ from karapace.utils import assert_never from karapace.version import __version__ from pathlib import Path -from typing import Callable, ContextManager, Final, IO, TypeVar -from typing_extensions import TypeAlias +from typing import ContextManager, Final, IO, TypeAlias, TypeVar import datetime import io diff --git a/src/karapace/backup/backends/v3/schema.py b/src/karapace/backup/backends/v3/schema.py index db4cc7862..b979d6472 100644 --- a/src/karapace/backup/backends/v3/schema.py +++ b/src/karapace/backup/backends/v3/schema.py @@ -8,7 +8,6 @@ from dataclasses import field from karapace.avro_dataclasses.models import AvroModel from karapace.dataclasses import default_dataclass -from typing import Optional import datetime import enum @@ -53,7 +52,7 @@ class Metadata(AvroModel): finished_at: datetime.datetime record_count: int = field(metadata={"type": "int"}) topic_name: str - topic_id: Optional[uuid.UUID] + topic_id: uuid.UUID | None partition_count: int = field(metadata={"type": "int"}) replication_factor: int = field(metadata={"type": "int"}) topic_configurations: Mapping[str, str] @@ -77,8 +76,8 @@ class Header(AvroModel): @default_dataclass class Record(AvroModel): - key: Optional[bytes] - value: Optional[bytes] + key: bytes | None + value: bytes | None headers: tuple[Header, ...] offset: int = field(metadata={"type": "long"}) timestamp: int = field(metadata={"type": "long"}) @@ -87,7 +86,7 @@ class Record(AvroModel): # of records. When restoring, we accumulate parsed records until # encountering a checkpoint, verify the running checksum against it, and # only then produce the verified records to Kafka. - checksum_checkpoint: Optional[bytes] + checksum_checkpoint: bytes | None def __post_init__(self) -> None: assert self.offset >= 0 diff --git a/src/karapace/backup/backends/writer.py b/src/karapace/backup/backends/writer.py index 927077e2b..2bbf88ce5 100644 --- a/src/karapace/backup/backends/writer.py +++ b/src/karapace/backup/backends/writer.py @@ -8,8 +8,7 @@ from confluent_kafka import Message from karapace.backup.safe_writer import bytes_writer, str_writer from pathlib import Path -from typing import ContextManager, Generic, IO, Literal, TypeVar -from typing_extensions import TypeAlias +from typing import ContextManager, Generic, IO, Literal, TypeAlias, TypeVar import abc import contextlib diff --git a/src/karapace/backup/safe_writer.py b/src/karapace/backup/safe_writer.py index d8338f5ae..78e08ea99 100644 --- a/src/karapace/backup/safe_writer.py +++ b/src/karapace/backup/safe_writer.py @@ -7,8 +7,7 @@ from collections.abc import Generator from pathlib import Path from tempfile import mkstemp, TemporaryDirectory -from typing import Final, IO, Literal -from typing_extensions import TypeAlias +from typing import Final, IO, Literal, TypeAlias import contextlib import os diff --git a/src/karapace/client.py b/src/karapace/client.py index 23a9e157a..0d4213eb1 100644 --- a/src/karapace/client.py +++ b/src/karapace/client.py @@ -5,9 +5,8 @@ See LICENSE for details """ from aiohttp import BasicAuth, ClientSession -from collections.abc import Awaitable, Mapping +from collections.abc import Awaitable, Callable, Mapping from karapace.typing import JsonData -from typing import Callable, Optional, Union from urllib.parse import urljoin import logging @@ -19,7 +18,7 @@ LOG = logging.getLogger(__name__) -async def _get_aiohttp_client(*, auth: Optional[BasicAuth] = None) -> ClientSession: +async def _get_aiohttp_client(*, auth: BasicAuth | None = None) -> ClientSession: return ClientSession(auth=auth) @@ -28,7 +27,7 @@ def __init__( self, status: int, json_result: JsonData, - headers: Optional[Mapping] = None, + headers: Mapping | None = None, ) -> None: self.status_code = status self.json_result = json_result @@ -48,10 +47,10 @@ def ok(self) -> bool: class Client: def __init__( self, - server_uri: Optional[str] = None, + server_uri: str | None = None, client_factory: Callable[..., Awaitable[ClientSession]] = _get_aiohttp_client, - server_ca: Optional[str] = None, - session_auth: Optional[BasicAuth] = None, + server_ca: str | None = None, + session_auth: BasicAuth | None = None, ) -> None: self.server_uri = server_uri or "" self.session_auth = session_auth @@ -61,13 +60,13 @@ def __init__( # Instead we wait for the first query in async context and lazy-initialize aiohttp client. self.client_factory = client_factory - self.ssl_mode: Union[None, bool, ssl.SSLContext] + self.ssl_mode: None | bool | ssl.SSLContext if server_ca is None: self.ssl_mode = False else: self.ssl_mode = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) self.ssl_mode.load_verify_locations(cafile=server_ca) - self._client: Optional[ClientSession] = None + self._client: ClientSession | None = None def path_for(self, path: Path) -> str: return urljoin(self.server_uri, path) @@ -89,9 +88,9 @@ async def get( self, path: Path, json: JsonData = None, - headers: Optional[Headers] = None, - auth: Optional[BasicAuth] = None, - params: Optional[Mapping[str, str]] = None, + headers: Headers | None = None, + auth: BasicAuth | None = None, + params: Mapping[str, str] | None = None, json_response: bool = True, ) -> Result: path = self.path_for(path) @@ -113,8 +112,8 @@ async def get( async def delete( self, path: Path, - headers: Optional[Headers] = None, - auth: Optional[BasicAuth] = None, + headers: Headers | None = None, + auth: BasicAuth | None = None, ) -> Result: path = self.path_for(path) if not headers: @@ -133,8 +132,8 @@ async def post( self, path: Path, json: JsonData, - headers: Optional[Headers] = None, - auth: Optional[BasicAuth] = None, + headers: Headers | None = None, + auth: BasicAuth | None = None, ) -> Result: path = self.path_for(path) if not headers: @@ -155,8 +154,8 @@ async def put( self, path: Path, json: JsonData, - headers: Optional[Headers] = None, - auth: Optional[BasicAuth] = None, + headers: Headers | None = None, + auth: BasicAuth | None = None, ) -> Result: path = self.path_for(path) if not headers: @@ -177,8 +176,8 @@ async def put_with_data( self, path: Path, data: JsonData, - headers: Optional[Headers], - auth: Optional[BasicAuth] = None, + headers: Headers | None, + auth: BasicAuth | None = None, ) -> Result: path = self.path_for(path) client = await self.get_client() diff --git a/src/karapace/compatibility/jsonschema/types.py b/src/karapace/compatibility/jsonschema/types.py index 91e6d91ba..9d8b7fc14 100644 --- a/src/karapace/compatibility/jsonschema/types.py +++ b/src/karapace/compatibility/jsonschema/types.py @@ -2,9 +2,10 @@ Copyright (c) 2023 Aiven Ltd See LICENSE for details """ +from collections.abc import Callable from dataclasses import dataclass from enum import Enum, unique -from typing import Callable, Generic, TypeVar +from typing import Generic, TypeVar T = TypeVar("T") diff --git a/src/karapace/compatibility/jsonschema/utils.py b/src/karapace/compatibility/jsonschema/utils.py index 486af0719..b26bf8843 100644 --- a/src/karapace/compatibility/jsonschema/utils.py +++ b/src/karapace/compatibility/jsonschema/utils.py @@ -5,7 +5,7 @@ from copy import copy from jsonschema import Draft7Validator from karapace.compatibility.jsonschema.types import BooleanSchema, Instance, Keyword, Subschema -from typing import Any, Optional, TypeVar, Union +from typing import Any, TypeVar, Union import re @@ -53,7 +53,7 @@ def normalize_schema_rec(validator: Draft7Validator, original_schema: Any) -> An return normalized -def maybe_get_subschemas_and_type(schema: Any) -> Optional[tuple[list[Any], Subschema]]: +def maybe_get_subschemas_and_type(schema: Any) -> tuple[list[Any], Subschema] | None: """If schema contains `anyOf`, `allOf`, or `oneOf`, return it. This will also normalized schemas with a list of types to a `anyOf`, e..g: @@ -218,7 +218,7 @@ def is_tuple_without_additional_items(schema: Any) -> bool: return is_tuple(schema) and is_false_schema(additional_items) -def gt(left: Optional[int], right: Optional[int]) -> bool: +def gt(left: int | None, right: int | None) -> bool: """Predicate greater-than that checks for nullables. When `left` is writer and `right` is reader, this can be used to check for @@ -256,11 +256,11 @@ def gt(left: Optional[int], right: Optional[int]) -> bool: return bool(left is not None and right is not None and left > right) -def lt(left: Optional[int], right: Optional[int]) -> bool: +def lt(left: int | None, right: int | None) -> bool: return gt(right, left) # pylint: disable=arguments-out-of-order -def ne(writer: Optional[T], reader: Optional[T]) -> bool: +def ne(writer: T | None, reader: T | None) -> bool: """Predicate not-equals that checks for nullables. Predicate used to check for incompatibility in constraints that accept @@ -288,7 +288,7 @@ def ne(writer: Optional[T], reader: Optional[T]) -> bool: return bool(reader is not None and writer is not None and reader != writer) -def introduced_constraint(reader: Optional[T], writer: Optional[T]) -> bool: +def introduced_constraint(reader: T | None, writer: T | None) -> bool: """True if `writer` did *not* have the constraint but `reader` introduced it. A constraint limits the value domain, because of that objects that were diff --git a/src/karapace/dataclasses.py b/src/karapace/dataclasses.py index f64ccd94a..d6be6c5c7 100644 --- a/src/karapace/dataclasses.py +++ b/src/karapace/dataclasses.py @@ -10,8 +10,6 @@ from typing import TypeVar from typing_extensions import dataclass_transform -import sys - T = TypeVar("T") @@ -20,11 +18,8 @@ kw_only_default=True, ) def default_dataclass(cls: type[T]) -> type[T]: - if sys.version_info >= (3, 10): - return dataclass( - frozen=True, - slots=True, - kw_only=True, - )(cls) - - return dataclass(frozen=True)(cls) + return dataclass( + frozen=True, + slots=True, + kw_only=True, + )(cls) diff --git a/src/karapace/instrumentation/prometheus.py b/src/karapace/instrumentation/prometheus.py index 90d260057..2e6acca30 100644 --- a/src/karapace/instrumentation/prometheus.py +++ b/src/karapace/instrumentation/prometheus.py @@ -9,10 +9,10 @@ from __future__ import annotations from aiohttp.web import middleware, Request, Response -from collections.abc import Awaitable +from collections.abc import Awaitable, Callable from karapace.rapu import RestApp from prometheus_client import CollectorRegistry, Counter, Gauge, generate_latest, Histogram -from typing import Callable, Final +from typing import Final import logging import time diff --git a/src/karapace/kafka/common.py b/src/karapace/kafka/common.py index add44eea3..c541cbac3 100644 --- a/src/karapace/kafka/common.py +++ b/src/karapace/kafka/common.py @@ -14,10 +14,10 @@ KafkaUnavailableError, NoBrokersAvailable, ) -from collections.abc import Iterable +from collections.abc import Callable, Iterable from concurrent.futures import Future from confluent_kafka.error import KafkaError, KafkaException -from typing import Any, Callable, Literal, NoReturn, Protocol, TypedDict, TypeVar +from typing import Any, Literal, NoReturn, Protocol, TypedDict, TypeVar from typing_extensions import Unpack import logging diff --git a/src/karapace/kafka/consumer.py b/src/karapace/kafka/consumer.py index 4bf4cde54..aa6d7ce4d 100644 --- a/src/karapace/kafka/consumer.py +++ b/src/karapace/kafka/consumer.py @@ -6,12 +6,12 @@ from __future__ import annotations from aiokafka.errors import IllegalStateError, KafkaTimeoutError -from collections.abc import Iterable +from collections.abc import Callable, Iterable from confluent_kafka import Consumer, Message, TopicPartition from confluent_kafka.admin import PartitionMetadata from confluent_kafka.error import KafkaException from karapace.kafka.common import _KafkaConfigMixin, KafkaClientParams, raise_from_kafkaexception -from typing import Any, Callable, TypeVar +from typing import Any, TypeVar from typing_extensions import Unpack import asyncio diff --git a/src/karapace/kafka_rest_apis/__init__.py b/src/karapace/kafka_rest_apis/__init__.py index f41f53842..ccceaa513 100644 --- a/src/karapace/kafka_rest_apis/__init__.py +++ b/src/karapace/kafka_rest_apis/__init__.py @@ -16,6 +16,7 @@ ) from binascii import Error as B64DecodeError from collections import namedtuple +from collections.abc import Callable from confluent_kafka.error import KafkaException from contextlib import AsyncExitStack from http import HTTPStatus @@ -44,7 +45,7 @@ ) from karapace.typing import NameStrategy, SchemaId, Subject, SubjectType from karapace.utils import convert_to_int, json_encode -from typing import Callable, TypedDict +from typing import TypedDict import asyncio import base64 diff --git a/src/karapace/kafka_rest_apis/schema_cache.py b/src/karapace/kafka_rest_apis/schema_cache.py index 6f4d8b45a..cc94f2d6c 100644 --- a/src/karapace/kafka_rest_apis/schema_cache.py +++ b/src/karapace/kafka_rest_apis/schema_cache.py @@ -8,14 +8,14 @@ from collections.abc import MutableMapping from karapace.schema_models import TypedSchema from karapace.typing import SchemaId, Subject -from typing import Final, Optional +from typing import Final import hashlib class SchemaCacheProtocol(ABC): @abstractmethod - def get_schema_id(self, schema: TypedSchema) -> Optional[SchemaId]: + def get_schema_id(self, schema: TypedSchema) -> SchemaId | None: pass @abstractmethod @@ -27,11 +27,11 @@ def set_schema(self, schema_id: SchemaId, schema: TypedSchema) -> None: pass @abstractmethod - def get_schema(self, schema_id: SchemaId) -> Optional[TypedSchema]: + def get_schema(self, schema_id: SchemaId) -> TypedSchema | None: pass @abstractmethod - def get_schema_str(self, schema_id: SchemaId) -> Optional[str]: + def get_schema_str(self, schema_id: SchemaId) -> str | None: pass @@ -40,7 +40,7 @@ def __init__(self) -> None: self._topic_cache: dict[Subject, SchemaCache] = {} self._empty_schema_cache: Final = EmptySchemaCache() - def get_schema_id(self, topic: Subject, schema: TypedSchema) -> Optional[SchemaId]: + def get_schema_id(self, topic: Subject, schema: TypedSchema) -> SchemaId | None: return self._topic_cache.get(topic, self._empty_schema_cache).get_schema_id(schema) def has_schema_id(self, topic: Subject, schema_id: SchemaId) -> bool: @@ -50,11 +50,11 @@ def set_schema(self, topic: str, schema_id: SchemaId, schema: TypedSchema) -> No schema_cache_with_defaults = self._topic_cache.setdefault(Subject(topic), SchemaCache()) schema_cache_with_defaults.set_schema(schema_id, schema) - def get_schema(self, topic: Subject, schema_id: SchemaId) -> Optional[TypedSchema]: + def get_schema(self, topic: Subject, schema_id: SchemaId) -> TypedSchema | None: schema_cache = self._topic_cache.get(topic, self._empty_schema_cache) return schema_cache.get_schema(schema_id) - def get_schema_str(self, topic: Subject, schema_id: SchemaId) -> Optional[str]: + def get_schema_str(self, topic: Subject, schema_id: SchemaId) -> str | None: schema_cache = self._topic_cache.get(topic, self._empty_schema_cache) return schema_cache.get_schema_str(schema_id) @@ -64,7 +64,7 @@ def __init__(self) -> None: self._schema_hash_str_to_id: dict[str, SchemaId] = {} self._id_to_schema_str: MutableMapping[SchemaId, TypedSchema] = TTLCache(maxsize=100, ttl=600) - def get_schema_id(self, schema: TypedSchema) -> Optional[SchemaId]: + def get_schema_id(self, schema: TypedSchema) -> SchemaId | None: fingerprint = hashlib.sha1(str(schema).encode("utf8")).hexdigest() maybe_id = self._schema_hash_str_to_id.get(fingerprint) @@ -83,10 +83,10 @@ def set_schema(self, schema_id: SchemaId, schema: TypedSchema) -> None: self._schema_hash_str_to_id[fingerprint] = schema_id self._id_to_schema_str[schema_id] = schema - def get_schema(self, schema_id: SchemaId) -> Optional[TypedSchema]: + def get_schema(self, schema_id: SchemaId) -> TypedSchema | None: return self._id_to_schema_str.get(schema_id) - def get_schema_str(self, schema_id: SchemaId) -> Optional[str]: + def get_schema_str(self, schema_id: SchemaId) -> str | None: maybe_schema = self.get_schema(schema_id) return None if maybe_schema is None else str(maybe_schema) diff --git a/src/karapace/karapace.py b/src/karapace/karapace.py index f486b1903..86f5a1876 100644 --- a/src/karapace/karapace.py +++ b/src/karapace/karapace.py @@ -8,7 +8,7 @@ from __future__ import annotations from aiohttp.web_request import Request -from collections.abc import Awaitable +from collections.abc import Awaitable, Callable from functools import partial from http import HTTPStatus from karapace.config import Config @@ -17,8 +17,7 @@ from karapace.typing import JsonObject from karapace.utils import json_encode from karapace.version import __version__ -from typing import Callable, NoReturn -from typing_extensions import TypeAlias +from typing import NoReturn, TypeAlias import aiohttp.web import time diff --git a/src/karapace/key_format.py b/src/karapace/key_format.py index 64f0e525a..c56c5a089 100644 --- a/src/karapace/key_format.py +++ b/src/karapace/key_format.py @@ -10,7 +10,7 @@ from karapace.typing import ArgJsonObject from karapace.utils import json_encode from types import MappingProxyType -from typing import Final, Optional +from typing import Final # used by the OrderedDict for the relative order of keys. SCHEMA_KEY_ORDER: Final[tuple[str, str, str, str]] = ("keytype", "subject", "version", "magic") @@ -67,7 +67,7 @@ def get_keymode(self) -> KeyMode: def format_key( self, key: ArgJsonObject, - keymode: Optional[KeyMode] = None, + keymode: KeyMode | None = None, ) -> bytes: """Format key by the given keymode. diff --git a/src/karapace/messaging.py b/src/karapace/messaging.py index d46f1a621..af4a745df 100644 --- a/src/karapace/messaging.py +++ b/src/karapace/messaging.py @@ -12,7 +12,7 @@ from karapace.offset_watcher import OffsetWatcher from karapace.utils import json_encode from karapace.version import __version__ -from typing import Any, Final, Optional, Union +from typing import Any, Final import logging import time @@ -23,7 +23,7 @@ class KarapaceProducer: def __init__(self, *, config: Config, offset_watcher: OffsetWatcher, key_formatter: KeyFormatter): - self._producer: Optional[KafkaProducer] = None + self._producer: KafkaProducer | None = None self._config = config self._offset_watcher = offset_watcher self._key_formatter = key_formatter @@ -60,7 +60,7 @@ def close(self) -> None: if self._producer is not None: self._producer.flush() - def _send_kafka_message(self, key: Union[bytes, str], value: Union[bytes, str]) -> None: + def _send_kafka_message(self, key: bytes | str, value: bytes | str) -> None: assert self._producer is not None if isinstance(key, str): @@ -103,9 +103,9 @@ def _send_kafka_message(self, key: Union[bytes, str], value: Union[bytes, str]) ) ) - def send_message(self, *, key: dict[str, Any], value: Optional[dict[str, Any]]) -> None: + def send_message(self, *, key: dict[str, Any], value: dict[str, Any] | None) -> None: key_bytes = self._key_formatter.format_key(key) - value_bytes: Union[bytes, str] = b"" + value_bytes: bytes | str = b"" if value is not None: value_bytes = json_encode(value, binary=True, compact=True) self._send_kafka_message(key=key_bytes, value=value_bytes) diff --git a/src/karapace/protobuf/compare_type_storage.py b/src/karapace/protobuf/compare_type_storage.py index 4ab651f9a..a48a8993f 100644 --- a/src/karapace/protobuf/compare_type_storage.py +++ b/src/karapace/protobuf/compare_type_storage.py @@ -14,7 +14,7 @@ from karapace.protobuf.message_element import MessageElement -def compute_name(t: ProtoType, result_path: list[str], package_name: str, types: dict) -> Optional[str]: +def compute_name(t: ProtoType, result_path: list[str], package_name: str, types: dict) -> str | None: string = t.string if string.startswith("."): @@ -41,8 +41,8 @@ def __init__(self, self_package_name: str, other_package_name: str, result: Comp self.self_package_name = self_package_name or "" self.other_package_name = other_package_name or "" - self.self_types: dict[str, Union[TypeRecord, TypeRecordMap]] = {} - self.other_types: dict[str, Union[TypeRecord, TypeRecordMap]] = {} + self.self_types: dict[str, TypeRecord | TypeRecordMap] = {} + self.other_types: dict[str, TypeRecord | TypeRecordMap] = {} self.locked_messages: list["MessageElement"] = [] self.environment: list["MessageElement"] = [] self.result = result @@ -58,8 +58,8 @@ def add_a_type(self, prefix: str, package_name: str, type_element: TypeElement, if isinstance(type_element, MessageElement): # add support of MapEntry messages if "map_entry" in type_element.options: - key: Optional[FieldElement] = next((f for f in type_element.fields if f.name == "key"), None) - value: Optional[FieldElement] = next((f for f in type_element.fields if f.name == "value"), None) + key: FieldElement | None = next((f for f in type_element.fields if f.name == "key"), None) + value: FieldElement | None = next((f for f in type_element.fields if f.name == "value"), None) types[name] = TypeRecordMap(package_name, type_element, key, value) else: types[name] = TypeRecord(package_name, type_element) @@ -89,7 +89,7 @@ def get_other_type(self, t: ProtoType) -> Union[None, "TypeRecord", "TypeRecordM return type_record return None - def self_type_short_name(self, t: ProtoType) -> Optional[str]: + def self_type_short_name(self, t: ProtoType) -> str | None: name = compute_name(t, self.result.path, self.self_package_name, self.self_types) if name is None: raise IllegalArgumentException(f"Cannot determine message type {t}") @@ -101,7 +101,7 @@ def self_type_short_name(self, t: ProtoType) -> Optional[str]: return name[(len(package_name) + 1) :] return name - def other_type_short_name(self, t: ProtoType) -> Optional[str]: + def other_type_short_name(self, t: ProtoType) -> str | None: name = compute_name(t, self.result.path, self.other_package_name, self.other_types) if name is None: raise IllegalArgumentException(f"Cannot determine message type {t}") diff --git a/src/karapace/protobuf/io.py b/src/karapace/protobuf/io.py index 89cdd26f1..d58c52415 100644 --- a/src/karapace/protobuf/io.py +++ b/src/karapace/protobuf/io.py @@ -15,8 +15,8 @@ from karapace.protobuf.type_element import TypeElement from multiprocessing import Process, Queue from pathlib import Path -from typing import Final, Protocol -from typing_extensions import Self, TypeAlias +from typing import Final, Protocol, TypeAlias +from typing_extensions import Self import hashlib import importlib diff --git a/src/karapace/protobuf/option_reader.py b/src/karapace/protobuf/option_reader.py index 0246ad67f..a3e12b614 100644 --- a/src/karapace/protobuf/option_reader.py +++ b/src/karapace/protobuf/option_reader.py @@ -7,7 +7,6 @@ from dataclasses import dataclass from karapace.protobuf.option_element import OptionElement from karapace.protobuf.syntax_reader import SyntaxReader -from typing import Union @dataclass @@ -130,7 +129,7 @@ def read_map(self, open_brace: str, close_brace: str, key_value_separator: str) self.reader.peek_char(";") @staticmethod - def add_to_list(_list: list, value: Union[list, str]) -> None: + def add_to_list(_list: list, value: list | str) -> None: """Adds an object or objects to a List.""" if isinstance(value, list): for v in list(value): diff --git a/src/karapace/protobuf/proto_file_element.py b/src/karapace/protobuf/proto_file_element.py index ed9f638cd..889b577b0 100644 --- a/src/karapace/protobuf/proto_file_element.py +++ b/src/karapace/protobuf/proto_file_element.py @@ -15,10 +15,10 @@ from karapace.protobuf.service_element import ServiceElement from karapace.protobuf.syntax import Syntax from karapace.protobuf.type_element import TypeElement -from typing import NewType, Optional +from typing import NewType -def _collect_dependencies_types(compare_types: CompareTypes, dependencies: Optional[dict[str, Dependency]], is_self: bool): +def _collect_dependencies_types(compare_types: CompareTypes, dependencies: dict[str, Dependency] | None, is_self: bool): for dep in dependencies.values(): types: list[TypeElement] = dep.schema.schema.proto_file_element.types sub_deps = dep.schema.schema.dependencies @@ -46,14 +46,14 @@ class ProtoFileElement: def __init__( self, location: Location, - package_name: Optional[PackageName] = None, - syntax: Optional[Syntax] = None, - imports: Optional[Sequence[TypeName]] = None, - public_imports: Optional[Sequence[TypeName]] = None, - types: Optional[Sequence[TypeElement]] = None, - services: Optional[Sequence[ServiceElement]] = None, - extend_declarations: Optional[Sequence[ExtendElement]] = None, - options: Optional[Sequence[OptionElement]] = None, + package_name: PackageName | None = None, + syntax: Syntax | None = None, + imports: Sequence[TypeName] | None = None, + public_imports: Sequence[TypeName] | None = None, + types: Sequence[TypeElement] | None = None, + services: Sequence[ServiceElement] | None = None, + extend_declarations: Sequence[ExtendElement] | None = None, + options: Sequence[OptionElement] | None = None, ) -> None: if types is None: types = list() @@ -133,8 +133,8 @@ def compare( self, other: "ProtoFileElement", result: CompareResult, - self_dependencies: Optional[dict[str, Dependency]] = None, - other_dependencies: Optional[dict[str, Dependency]] = None, + self_dependencies: dict[str, Dependency] | None = None, + other_dependencies: dict[str, Dependency] | None = None, ) -> CompareResult: from karapace.protobuf.compare_type_lists import compare_type_lists diff --git a/src/karapace/protobuf/proto_parser.py b/src/karapace/protobuf/proto_parser.py index f5a002aa5..11e5fdf63 100644 --- a/src/karapace/protobuf/proto_parser.py +++ b/src/karapace/protobuf/proto_parser.py @@ -28,7 +28,6 @@ from karapace.protobuf.syntax_reader import SyntaxReader from karapace.protobuf.type_element import TypeElement from karapace.protobuf.utils import MAX_TAG_VALUE -from typing import Optional, Union class Context(Enum): @@ -79,8 +78,8 @@ def __init__(self, location: Location, data: str) -> None: self.extends_list: list[str] = [] self.options: list[str] = [] self.declaration_count = 0 - self.syntax: Optional[Syntax] = None - self.package_name: Optional[str] = None + self.syntax: Syntax | None = None + self.package_name: str | None = None self.prefix = "" self.data = data self.public_imports: list[str] = [] @@ -127,21 +126,21 @@ def read_proto_file(self) -> ProtoFileElement: def read_declaration( self, documentation: str, context: Context - ) -> Union[ - None, - OptionElement, - ReservedElement, - RpcElement, - MessageElement, - EnumElement, - EnumConstantElement, - ServiceElement, - ExtendElement, - ExtensionsElement, - OneOfElement, - GroupElement, - FieldElement, - ]: + ) -> ( + None + | OptionElement + | ReservedElement + | RpcElement + | MessageElement + | EnumElement + | EnumConstantElement + | ServiceElement + | ExtendElement + | ExtensionsElement + | OneOfElement + | GroupElement + | FieldElement + ): index = self.declaration_count self.declaration_count += 1 @@ -155,21 +154,21 @@ def read_declaration( # TODO(benoit) Let's better parse the proto keywords. We are pretty weak when field/constants # are named after any of the label we check here. - result: Union[ - None, - OptionElement, - ReservedElement, - RpcElement, - MessageElement, - EnumElement, - EnumConstantElement, - ServiceElement, - ExtendElement, - ExtensionsElement, - OneOfElement, - GroupElement, - FieldElement, - ] = None + result: ( + None + | OptionElement + | ReservedElement + | RpcElement + | MessageElement + | EnumElement + | EnumConstantElement + | ServiceElement + | ExtendElement + | ExtensionsElement + | OneOfElement + | GroupElement + | FieldElement + ) = None # pylint no-else-return if label == "package" and context.permits_package(): self.package_name = self.reader.read_name() @@ -349,8 +348,8 @@ def read_enum_element(self, location: Location, documentation: str) -> EnumEleme pass return EnumElement(location, name, documentation, options, constants) - def read_field(self, documentation: str, location: Location, word: str) -> Union[GroupElement, FieldElement]: - label: Union[None, Field.Label] + def read_field(self, documentation: str, location: Location, word: str) -> GroupElement | FieldElement: + label: None | Field.Label atype: str if word == "required": self.reader.expect_with_location( @@ -382,7 +381,7 @@ def read_field(self, documentation: str, location: Location, word: str) -> Union return self.read_field_with_label(location, documentation, label, atype) def read_field_with_label( - self, location: Location, documentation: str, label: Union[None, Field.Label], atype: str + self, location: Location, documentation: str, label: None | Field.Label, atype: str ) -> FieldElement: """Reads an field declaration and returns it.""" name = self.reader.read_name() @@ -410,20 +409,20 @@ def read_field_with_label( options, ) - def strip_default(self, options: list) -> Union[str, None]: + def strip_default(self, options: list) -> str | None: """Defaults aren't options.""" return self.strip_value("default", options) - def strip_json_name(self, options: list) -> Union[None, str]: + def strip_json_name(self, options: list) -> None | str: """`json_name` isn't an option.""" return self.strip_value("json_name", options) @staticmethod - def strip_value(name: str, options: list) -> Union[None, str]: + def strip_value(name: str, options: list) -> None | str: """This finds an option named [name], removes, and returns it. Returns None if no [name] option is present. """ - result: Union[None, str] = None + result: None | str = None for element in options[:]: if element.name == name: options.remove(element) @@ -464,7 +463,7 @@ def read_group( self, location: Location, documentation: str, - label: Union[None, Field.Label], + label: None | Field.Label, ) -> GroupElement: name = self.reader.read_word() self.reader.require("=") diff --git a/src/karapace/protobuf/proto_type.py b/src/karapace/protobuf/proto_type.py index 9dad49163..9eb97ce4e 100644 --- a/src/karapace/protobuf/proto_type.py +++ b/src/karapace/protobuf/proto_type.py @@ -141,13 +141,13 @@ def to_kind(self) -> OptionElement.Kind: }.get(self.simple_name, OptionElement.Kind.ENUM) @property - def enclosing_type_or_package(self) -> Optional[str]: + def enclosing_type_or_package(self) -> str | None: """Returns the enclosing type, or None if self type is not nested in another type.""" dot = self.string.rfind(".") return None if (dot == -1) else self.string[:dot] @property - def type_url(self) -> Optional[str]: + def type_url(self) -> str | None: """Returns a string like "type.googleapis.com/packagename.messagename" or None if self type is a scalar or a map. diff --git a/src/karapace/protobuf/syntax_reader.py b/src/karapace/protobuf/syntax_reader.py index 13c3238e9..4c43237d0 100644 --- a/src/karapace/protobuf/syntax_reader.py +++ b/src/karapace/protobuf/syntax_reader.py @@ -6,7 +6,7 @@ # wire-library/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/parser/SyntaxReader.kt from karapace.protobuf.exception import IllegalStateException from karapace.protobuf.location import Location -from typing import NoReturn, Union +from typing import NoReturn class SyntaxReader: @@ -34,7 +34,7 @@ def require(self, c: str) -> None: """Reads a non-whitespace character 'c'""" self.expect(self.read_char() == c, f"expected '{c}'") - def peek_char(self, ch: str = None) -> Union[bool, str]: + def peek_char(self, ch: str = None) -> bool | str: """Peeks a non-whitespace character and returns it. The only difference between this and [read_char] is that this doesn't consume the char. """ @@ -80,7 +80,7 @@ def read_quoted_string(self) -> str: self.expect(self.pos < len(self.data), "unexpected end of file") c = self.data[self.pos] self.pos += 1 - d: Union[str, None] = { + d: str | None = { "a": "\u0007", # Alert. "b": "\b", # Backspace. "f": "\u000c", # Form feed. diff --git a/src/karapace/rapu.py b/src/karapace/rapu.py index d236671fa..39504be3c 100644 --- a/src/karapace/rapu.py +++ b/src/karapace/rapu.py @@ -7,12 +7,13 @@ See LICENSE for details """ from accept_types import get_best_match +from collections.abc import Callable from http import HTTPStatus from karapace.config import Config, create_server_ssl_context from karapace.statsd import StatsClient from karapace.utils import json_decode, json_encode from karapace.version import __version__ -from typing import Callable, NoReturn, Optional, overload, Union +from typing import NoReturn, overload import aiohttp import aiohttp.web @@ -66,21 +67,21 @@ def __init__( headers: dict[str, str], path_for_stats: str, method: str, - content_type: Optional[str] = None, - accepts: Optional[str] = None, + content_type: str | None = None, + accepts: str | None = None, ): self.url = url self.headers = headers - self._header_cache: dict[str, Optional[str]] = {} + self._header_cache: dict[str, str | None] = {} self.query = query self.content_type = content_type self.accepts = accepts self.path_for_stats = path_for_stats self.method = method - self.json: Optional[dict] = None + self.json: dict | None = None @overload - def get_header(self, header: str) -> Optional[str]: + def get_header(self, header: str) -> str | None: ... @overload @@ -112,15 +113,15 @@ class HTTPResponse(Exception): in response handler callbacks.""" status: HTTPStatus - json: Union[None, list, dict] + json: None | list | dict def __init__( self, body, *, status: HTTPStatus = HTTPStatus.OK, - content_type: Optional[str] = None, - headers: Optional[dict[str, str]] = None, + content_type: str | None = None, + headers: dict[str, str] | None = None, ) -> None: self.body = body self.status = status @@ -159,7 +160,7 @@ def http_error(message, content_type: str, code: HTTPStatus) -> NoReturn: class RestApp: def __init__( - self, *, app_name: str, config: Config, not_ready_handler: Optional[Callable[[HTTPRequest], None]] = None + self, *, app_name: str, config: Config, not_ready_handler: Callable[[HTTPRequest], None] | None = None ) -> None: self.app_name = app_name self.config = config diff --git a/src/karapace/sentry/sentry_client_api.py b/src/karapace/sentry/sentry_client_api.py index 4ca9575c8..b423c189a 100644 --- a/src/karapace/sentry/sentry_client_api.py +++ b/src/karapace/sentry/sentry_client_api.py @@ -5,7 +5,7 @@ from __future__ import annotations from collections.abc import Mapping -from typing_extensions import TypeAlias +from typing import TypeAlias KarapaceSentryConfig: TypeAlias = "Mapping[str, object] | None" diff --git a/src/karapace/serialization.py b/src/karapace/serialization.py index b665072d2..1c6d75d05 100644 --- a/src/karapace/serialization.py +++ b/src/karapace/serialization.py @@ -7,7 +7,7 @@ from aiohttp import BasicAuth from avro.io import BinaryDecoder, BinaryEncoder, DatumReader, DatumWriter from cachetools import TTLCache -from collections.abc import MutableMapping +from collections.abc import Callable, MutableMapping from functools import lru_cache from google.protobuf.message import DecodeError from jsonschema import ValidationError @@ -22,7 +22,7 @@ from karapace.schema_references import LatestVersionReference, Reference, reference_from_mapping from karapace.typing import NameStrategy, SchemaId, Subject, SubjectType, Version from karapace.utils import json_decode, json_encode -from typing import Any, Callable +from typing import Any from urllib.parse import quote import asyncio diff --git a/src/karapace/typing.py b/src/karapace/typing.py index 7927657eb..23791e471 100644 --- a/src/karapace/typing.py +++ b/src/karapace/typing.py @@ -5,12 +5,11 @@ from __future__ import annotations from abc import ABC, abstractmethod -from collections.abc import Generator, Mapping, Sequence +from collections.abc import Callable, Generator, Mapping, Sequence from enum import Enum, unique from karapace.errors import InvalidVersion from pydantic import ValidationInfo -from typing import Any, Callable, ClassVar, NewType, Union -from typing_extensions import TypeAlias +from typing import Any, ClassVar, NewType, TypeAlias, Union import functools diff --git a/src/schema_registry/factory.py b/src/schema_registry/factory.py index ae104b854..12a80775d 100644 --- a/src/schema_registry/factory.py +++ b/src/schema_registry/factory.py @@ -2,7 +2,7 @@ Copyright (c) 2024 Aiven Ltd See LICENSE for details """ -from collections.abc import AsyncGenerator +from collections.abc import AsyncGenerator, Callable from contextlib import asynccontextmanager from dependency_injector.wiring import inject, Provide from fastapi import Depends, FastAPI @@ -17,7 +17,7 @@ from schema_registry.http_handlers import setup_exception_handlers from schema_registry.middlewares import setup_middlewares from schema_registry.routers.setup import setup_routers -from typing import AsyncContextManager, Callable +from typing import AsyncContextManager import logging diff --git a/src/schema_registry/middlewares/__init__.py b/src/schema_registry/middlewares/__init__.py index 7c0559687..8df42b04c 100644 --- a/src/schema_registry/middlewares/__init__.py +++ b/src/schema_registry/middlewares/__init__.py @@ -3,11 +3,10 @@ See LICENSE for details """ -from collections.abc import Awaitable +from collections.abc import Awaitable, Callable from fastapi import FastAPI, HTTPException, Request, Response from fastapi.responses import JSONResponse from karapace.content_type import check_schema_headers -from typing import Callable def setup_middlewares(app: FastAPI) -> None: diff --git a/tests/conftest.py b/tests/conftest.py index f6776d924..8413773fa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,6 @@ from pathlib import Path from schema_registry.container import SchemaRegistryContainer from tempfile import mkstemp -from typing import Optional import json import os @@ -22,7 +21,7 @@ VERSION_REGEX = "([0-9]+[.])*[0-9]+" -def pytest_assertrepr_compare(op, left, right) -> Optional[list[str]]: +def pytest_assertrepr_compare(op, left, right) -> list[str] | None: if isinstance(left, SchemaCompatibilityResult) and isinstance(right, SchemaCompatibilityResult) and op in ("==", "!="): lines = ["Comparing SchemaCompatibilityResult instances:"] diff --git a/tests/integration/test_schema_compatibility.py b/tests/integration/test_schema_compatibility.py index d71237d7c..d9044a1a7 100644 --- a/tests/integration/test_schema_compatibility.py +++ b/tests/integration/test_schema_compatibility.py @@ -4,12 +4,12 @@ """ from __future__ import annotations -from collections.abc import Coroutine +from collections.abc import Callable, Coroutine from dataclasses import dataclass from karapace.client import Client from karapace.typing import JsonObject, Subject from tests.base_testcase import BaseTestCase -from typing import Any, Callable, Final +from typing import Any, Final import json import logging diff --git a/tests/integration/utils/process.py b/tests/integration/utils/process.py index e3c36e412..d1769f5e2 100644 --- a/tests/integration/utils/process.py +++ b/tests/integration/utils/process.py @@ -5,7 +5,6 @@ from karapace.utils import Expiration from subprocess import Popen from tests.integration.utils.network import port_is_listening -from typing import Optional import os import signal @@ -35,7 +34,7 @@ def wait_for_port_subprocess( print(f"Server `{hostname}:{port}` listening after {elapsed} seconds") -def stop_process(proc: Optional[Popen]) -> None: +def stop_process(proc: Popen | None) -> None: if proc: try: os.kill(proc.pid, signal.SIGKILL) diff --git a/tests/unit/anonymize_schemas/test_anonymize_avro.py b/tests/unit/anonymize_schemas/test_anonymize_avro.py index 2dc8870dc..fbd51563b 100644 --- a/tests/unit/anonymize_schemas/test_anonymize_avro.py +++ b/tests/unit/anonymize_schemas/test_anonymize_avro.py @@ -5,7 +5,6 @@ See LICENSE for details """ from karapace.anonymize_schemas.anonymize_avro import anonymize -from typing import Union import json import pytest @@ -565,6 +564,6 @@ [EMPTY_STR, EMPTY_STR], ], ) -def test_anonymize(test_schema: str, expected_schema: Union[str, dict[str, str]]): +def test_anonymize(test_schema: str, expected_schema: str | dict[str, str]): res = anonymize(test_schema) assert res == expected_schema diff --git a/tests/unit/avro_dataclasses/test_introspect.py b/tests/unit/avro_dataclasses/test_introspect.py index b816cf128..79a9e503d 100644 --- a/tests/unit/avro_dataclasses/test_introspect.py +++ b/tests/unit/avro_dataclasses/test_introspect.py @@ -7,7 +7,7 @@ from enum import Enum from karapace.avro_dataclasses.introspect import field_schema, record_schema, UnsupportedAnnotation from karapace.avro_dataclasses.schema import FieldSchema -from typing import Final, Optional +from typing import Final import datetime import pytest @@ -32,8 +32,8 @@ class ValidRecord: int_field: int explicit_int_field: int = field(metadata={"type": "int"}) none_field: None - optional_field: Optional[int] - optional_bytes_field: Optional[bytes] + optional_field: int | None + optional_bytes_field: bytes | None enum_field: Symbols dt_field: datetime.datetime int_array: tuple[int, ...] diff --git a/tests/unit/avro_dataclasses/test_models.py b/tests/unit/avro_dataclasses/test_models.py index 4161b84a5..cba32b08c 100644 --- a/tests/unit/avro_dataclasses/test_models.py +++ b/tests/unit/avro_dataclasses/test_models.py @@ -4,7 +4,6 @@ """ from dataclasses import dataclass, field from karapace.avro_dataclasses.models import AvroModel -from typing import Optional import datetime import enum @@ -41,7 +40,7 @@ class HasList(AvroModel): @dataclass(frozen=True) class HasOptionalBytes(AvroModel): - value: Optional[bytes] + value: bytes | None class TestAvroModel: diff --git a/tests/unit/backup/test_api.py b/tests/unit/backup/test_api.py index 3df4d028d..bc8f15f7e 100644 --- a/tests/unit/backup/test_api.py +++ b/tests/unit/backup/test_api.py @@ -5,6 +5,7 @@ from __future__ import annotations from aiokafka.errors import KafkaError, TopicAlreadyExistsError +from collections.abc import Callable from karapace.backup.api import ( _admin, _consumer, @@ -26,7 +27,7 @@ from karapace.kafka.producer import KafkaProducer from pathlib import Path from types import FunctionType -from typing import Callable, cast, ContextManager +from typing import cast, ContextManager from unittest import mock from unittest.mock import MagicMock diff --git a/tests/unit/backup/test_poll_timeout.py b/tests/unit/backup/test_poll_timeout.py index ecd9bfce4..62660a3b2 100644 --- a/tests/unit/backup/test_poll_timeout.py +++ b/tests/unit/backup/test_poll_timeout.py @@ -4,14 +4,13 @@ """ from datetime import timedelta from karapace.backup.poll_timeout import PollTimeout -from typing import Union import pytest class TestPollTimeout: @pytest.mark.parametrize("it", ("PT0.999S", timedelta(milliseconds=999))) - def test_min_validation(self, it: Union[str, timedelta]) -> None: + def test_min_validation(self, it: str | timedelta) -> None: with pytest.raises( ValueError, match=r"^Poll timeout must be at least one second, got: 0:00:00.999000$", diff --git a/tests/unit/test_schema_models.py b/tests/unit/test_schema_models.py index 313f77daf..aa0b56b24 100644 --- a/tests/unit/test_schema_models.py +++ b/tests/unit/test_schema_models.py @@ -6,11 +6,12 @@ """ from avro.schema import Schema as AvroSchema +from collections.abc import Callable from karapace.errors import InvalidVersion, VersionNotFoundException from karapace.schema_models import parse_avro_schema_definition, SchemaVersion, TypedSchema, Versioner from karapace.schema_type import SchemaType from karapace.typing import Version, VersionTag -from typing import Any, Callable, Optional +from typing import Any import operator import pytest @@ -91,7 +92,7 @@ def schema_versions_factory( avro_schema: str, avro_schema_parsed: AvroSchema, ) -> Callable[[Version, dict[str, Any]], dict[Version, SchemaVersion]]: - def schema_versions(version: Version, schema_version_data: Optional[dict[str, Any]] = None): + def schema_versions(version: Version, schema_version_data: dict[str, Any] | None = None): schema_version_data = schema_version_data or dict() base_schema_version_data = dict( subject="test-topic", diff --git a/tests/unit/test_schema_reader.py b/tests/unit/test_schema_reader.py index 1134b6ae8..a245d026f 100644 --- a/tests/unit/test_schema_reader.py +++ b/tests/unit/test_schema_reader.py @@ -6,6 +6,7 @@ """ from _pytest.logging import LogCaptureFixture +from collections.abc import Callable from concurrent.futures import Future, ThreadPoolExecutor from confluent_kafka import Message from dataclasses import dataclass @@ -28,7 +29,6 @@ from pytest import MonkeyPatch from tests.base_testcase import BaseTestCase from tests.utils import schema_protobuf_invalid_because_corrupted, schema_protobuf_with_invalid_ref -from typing import Callable, Optional from unittest.mock import Mock import confluent_kafka @@ -334,7 +334,7 @@ class HealthCheckTestCase(BaseTestCase): consecutive_unexpected_errors: int consecutive_unexpected_errors_start: float healthy: bool - check_topic_error: Optional[Exception] = None + check_topic_error: Exception | None = None @pytest.mark.parametrize( diff --git a/tests/utils.py b/tests/utils.py index ecddea84e..940f9fe65 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,13 +4,14 @@ """ from aiohttp.client_exceptions import ClientOSError, ServerDisconnectedError from aiokafka.errors import TopicAlreadyExistsError +from collections.abc import Callable from karapace.client import Client from karapace.kafka.admin import KafkaAdminClient from karapace.protobuf.kotlin_wrapper import trim_margin from karapace.utils import Expiration from pathlib import Path from subprocess import Popen -from typing import Any, Callable, IO +from typing import Any, IO from urllib.parse import quote import asyncio diff --git a/website/README.rst b/website/README.rst index c333ba578..9b24823fc 100644 --- a/website/README.rst +++ b/website/README.rst @@ -6,7 +6,7 @@ A static HTML site, generated with Sphinx. You can find the website source in th Dependencies ------------ -You need Python 3.9+. Install the dependencies with ``pip``:: +You need Python 3.10+. Install the dependencies with ``pip``:: pip install -r requirements.txt