diff --git a/eventsourcingdb/__init__.py b/eventsourcingdb/__init__.py index 6a6f63d..99ca576 100644 --- a/eventsourcingdb/__init__.py +++ b/eventsourcingdb/__init__.py @@ -1,7 +1,13 @@ from .bound import Bound, BoundType from .client import Client from .container import Container -from .errors import ClientError, CustomError, InternalError, ServerError, ValidationError +from .errors import ( + ClientError, + CustomError, + InternalError, + ServerError, + ValidationError, +) from .event import Event, EventCandidate from .observe_events import ( IfEventIsMissingDuringObserve, @@ -9,7 +15,12 @@ ObserveFromLatestEvent, ) from .read_event_types import EventType -from .read_events import IfEventIsMissingDuringRead, Order, ReadEventsOptions, ReadFromLatestEvent +from .read_events import ( + IfEventIsMissingDuringRead, + Order, + ReadEventsOptions, + ReadFromLatestEvent, +) from .write_events import ( IsEventQlQueryTrue, IsSubjectOnEventId, diff --git a/eventsourcingdb/client.py b/eventsourcingdb/client.py index 578e498..d5d9ce4 100644 --- a/eventsourcingdb/client.py +++ b/eventsourcingdb/client.py @@ -1,28 +1,23 @@ +import json from collections import OrderedDict from collections.abc import AsyncGenerator - -from types import TracebackType -from typing import Any, TypeAlias, TypeVar - from http import HTTPStatus -import json +from types import TracebackType +from typing import Any, Self, TypeAlias, TypeVar +from .errors import CustomError, InternalError, ServerError, ValidationError +from .event import Event, EventCandidate +from .http_client import HttpClient, Response +from .is_event import is_event from .is_heartbeat import is_heartbeat from .is_stream_error import is_stream_error -from .is_event import is_event from .is_valid_server_header import is_valid_server_header -from .parse_raw_message import parse_raw_message -from .read_events import ReadEventsOptions - -from .errors import CustomError, InternalError, ServerError, ValidationError -from .event import Event, EventCandidate from .observe_events import ObserveEventsOptions +from .parse_raw_message import parse_raw_message from .read_event_types import EventType, is_event_type +from .read_events import ReadEventsOptions from .read_subjects import is_subject - from .write_events import Precondition -from .http_client import HttpClient, Response - T = TypeVar('T') @@ -35,7 +30,7 @@ SubjectStream: TypeAlias = AsyncGenerator[str, None] -class Client(): +class Client: def __init__( self, base_url: str, @@ -43,13 +38,13 @@ def __init__( ) -> None: self.__http_client = HttpClient(base_url=base_url, api_token=api_token) - async def __aenter__(self) -> 'Client': + async def __aenter__(self) -> Self: await self.__http_client.__aenter__() return self async def __aexit__( self, - exc_type: BaseException | None = None, + exc_type: type[BaseException] | None = None, exc_val: BaseException | None = None, exc_tb: TracebackType | None = None, ) -> None: diff --git a/eventsourcingdb/container.py b/eventsourcingdb/container.py index 7fded67..8f4e798 100644 --- a/eventsourcingdb/container.py +++ b/eventsourcingdb/container.py @@ -6,12 +6,14 @@ import docker import requests -from docker import DockerClient, errors -from cryptography.hazmat.primitives.asymmetric import ed25519 from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import ed25519 +from docker import DockerClient, errors from .client import Client +logger = logging.getLogger(__name__) + class Container: def __init__( @@ -33,19 +35,19 @@ def _cleanup_existing_containers(self) -> None: filters={"ancestor": f"{self._image_name}:{self._image_tag}"} ) except errors.APIError as e: - logging.warning("Warning: Error listing existing containers: %s", e) + logger.warning("Warning: Error listing existing containers: %s", e) return for container in containers: try: container.stop() except errors.APIError as e: - logging.warning("Warning: Error stopping container: %s", e) + logger.warning("Warning: Error stopping container: %s", e) try: container.remove() except errors.APIError as e: - logging.warning("Warning: Error removing container: %s", e) + logger.warning("Warning: Error removing container: %s", e) def _create_container(self) -> None: port_bindings = {f"{self._internal_port}/tcp": None} @@ -200,7 +202,7 @@ def _handle_image_pull_error(self, error) -> None: f"Could not pull image and no local image available: {error}" ) from error - logging.warning("Warning: Could not pull image: %s. Using locally cached image.", error) + logger.warning("Warning: Could not pull image: %s. Using locally cached image.", error) def stop(self) -> None: self._stop_and_remove_container() @@ -212,16 +214,16 @@ def _stop_and_remove_container(self) -> None: try: self._container.stop() except errors.NotFound as e: - logging.warning("Warning: Container not found while stopping: %s", e) + logger.warning("Warning: Container not found while stopping: %s", e) except errors.APIError as e: - logging.warning("Warning: API error while stopping container: %s", e) + logger.warning("Warning: API error while stopping container: %s", e) try: self._container.remove() except errors.NotFound as e: - logging.warning("Warning: Container not found while removing: %s", e) + logger.warning("Warning: Container not found while removing: %s", e) except errors.APIError as e: - logging.warning("Warning: API error while removing container: %s", e) + logger.warning("Warning: API error while removing container: %s", e) self._container = None self._mapped_port = None diff --git a/eventsourcingdb/event/event.py b/eventsourcingdb/event/event.py index 6da3823..a5da2c1 100644 --- a/eventsourcingdb/event/event.py +++ b/eventsourcingdb/event/event.py @@ -1,6 +1,6 @@ +import json from dataclasses import dataclass, field from datetime import datetime -import json from hashlib import sha256 from typing import Any, TypeVar @@ -108,16 +108,16 @@ def parse(unknown_object: dict) -> "Event": return event def verify_hash(self) -> None: - metadata = "|".join([ - self.spec_version, - self.event_id, - self.predecessor_hash, - self._time_from_server, - self.source, - self.subject, - self.type, - self.data_content_type, - ]) + metadata = ( + f"{self.spec_version}|" + f"{self.event_id}|" + f"{self.predecessor_hash}|" + f"{self._time_from_server}|" + f"{self.source}|" + f"{self.subject}|" + f"{self.type}|" + f"{self.data_content_type}" + ) metadata_bytes = metadata.encode("utf-8") data_bytes = json.dumps( diff --git a/eventsourcingdb/http_client/__init__.py b/eventsourcingdb/http_client/__init__.py index 06efa7e..878f044 100644 --- a/eventsourcingdb/http_client/__init__.py +++ b/eventsourcingdb/http_client/__init__.py @@ -4,8 +4,8 @@ from .response import Response __all__ = [ - "get_get_headers", - "get_post_headers", "HttpClient", "Response", + "get_get_headers", + "get_post_headers", ] diff --git a/eventsourcingdb/http_client/http_client.py b/eventsourcingdb/http_client/http_client.py index 9dba741..3511a50 100644 --- a/eventsourcingdb/http_client/http_client.py +++ b/eventsourcingdb/http_client/http_client.py @@ -1,4 +1,5 @@ from types import TracebackType +from typing import Self import aiohttp from aiohttp import ClientSession @@ -18,13 +19,13 @@ def __init__( self.__api_token = api_token self.__session: ClientSession | None = None - async def __aenter__(self) -> 'HttpClient': + async def __aenter__(self) -> Self: await self.__initialize() return self async def __aexit__( self, - exc_type: BaseException | None = None, + exc_type: type[BaseException] | None = None, exc_val: BaseException | None = None, exc_tb: TracebackType | None = None, ) -> None: diff --git a/eventsourcingdb/http_client/response.py b/eventsourcingdb/http_client/response.py index 1e64768..c57b075 100644 --- a/eventsourcingdb/http_client/response.py +++ b/eventsourcingdb/http_client/response.py @@ -1,5 +1,6 @@ from collections.abc import Mapping from http import HTTPStatus +from typing import Self import aiohttp from aiohttp import StreamReader @@ -11,14 +12,14 @@ class Response: def __init__(self, response: aiohttp.ClientResponse) -> None: self.__response: aiohttp.ClientResponse = response - async def __aenter__(self) -> "Response": + async def __aenter__(self) -> Self: return self async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: if not self.__response.closed: self.__response.close() - def __enter__(self) -> "Response": + def __enter__(self) -> Self: return self def __exit__(self, exc_type, exc_val, exc_tb) -> None: diff --git a/eventsourcingdb/is_valid_server_header.py b/eventsourcingdb/is_valid_server_header.py index ab21472..e8f723b 100644 --- a/eventsourcingdb/is_valid_server_header.py +++ b/eventsourcingdb/is_valid_server_header.py @@ -7,7 +7,4 @@ def is_valid_server_header(response: Response) -> bool: if not server_header: return False - if not server_header.startswith('EventSourcingDB/'): - return False - - return True + return server_header.startswith('EventSourcingDB/') diff --git a/eventsourcingdb/observe_events/observe_events_options.py b/eventsourcingdb/observe_events/observe_events_options.py index ae5bdc0..f565bc5 100644 --- a/eventsourcingdb/observe_events/observe_events_options.py +++ b/eventsourcingdb/observe_events/observe_events_options.py @@ -18,12 +18,11 @@ def validate(self) -> None: "ObserveEventsOptions are invalid: lower_bound must be a Bound object." ) - if self.from_latest_event is not None: - if self.lower_bound is not None: - raise ValidationError( - "ReadEventsOptions are invalid: " - "lowerBound and fromLatestEvent are mutually exclusive" - ) + if self.from_latest_event is not None and self.lower_bound is not None: + raise ValidationError( + "ReadEventsOptions are invalid: " + "lowerBound and fromLatestEvent are mutually exclusive" + ) def to_json(self) -> dict[str, Any]: result: dict[str, Any] = { diff --git a/eventsourcingdb/pandas.py b/eventsourcingdb/pandas.py index 37a6348..b53a5f9 100644 --- a/eventsourcingdb/pandas.py +++ b/eventsourcingdb/pandas.py @@ -1,4 +1,4 @@ -from typing import AsyncGenerator +from collections.abc import AsyncGenerator import pandas as pd diff --git a/pyproject.toml b/pyproject.toml index e9eab80..6c29b22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ dev = [ "pytest-timeout==2.4.0", "pytest-asyncio==1.4.0", "pytest-cov==7.1.0", - "ruff==0.15.22", + "ruff==0.16.0", "bandit==1.9.4", "pyright==1.1.411", "twine==6.2.0", diff --git a/tests/conftest.py b/tests/conftest.py index 22e1080..fe1455e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +from typing import ClassVar + import pytest_asyncio from eventsourcingdb import EventCandidate @@ -20,9 +22,9 @@ class TestData: LOGGED_IN_SUBJECT = "/users/logged-in" REGISTERED_TYPE = "io.thenativeweb.users.registered" LOGGED_IN_TYPE = "io.thenativeweb.users.logged-in" - JANE_DATA = {"name": "jane"} - JOHN_DATA = {"name": "john"} - APFEL_FRED_DATA = {"name": "apfel fred"} + JANE_DATA: ClassVar[dict[str, str]] = {"name": "jane"} + JOHN_DATA: ClassVar[dict[str, str]] = {"name": "john"} + APFEL_FRED_DATA: ClassVar[dict[str, str]] = {"name": "apfel fred"} TRACE_PARENT_1 = "00-10000000000000000000000000000000-1000000000000000-00" TRACE_PARENT_2 = "00-20000000000000000000000000000000-2000000000000000-00" TRACE_PARENT_3 = "00-30000000000000000000000000000000-3000000000000000-00" diff --git a/tests/event/test_verify_hash.py b/tests/event/test_verify_hash.py index 3f1435e..19f0651 100644 --- a/tests/event/test_verify_hash.py +++ b/tests/event/test_verify_hash.py @@ -1,8 +1,9 @@ +from hashlib import sha256 + import pytest from eventsourcingdb import EventCandidate from eventsourcingdb.errors.validation_error import ValidationError -from hashlib import sha256 from ..conftest import TestData from ..shared.database import Database @@ -50,7 +51,7 @@ async def test_fails_if_the_event_hash_is_invalid( written_event = written_events[0] - invalid_hash_data = "invalid data".encode("utf-8") + invalid_hash_data = b"invalid data" invalid_hash = sha256(invalid_hash_data).hexdigest() written_event.hash = invalid_hash diff --git a/tests/event/test_verify_signature.py b/tests/event/test_verify_signature.py index 759438e..2c9e25e 100644 --- a/tests/event/test_verify_signature.py +++ b/tests/event/test_verify_signature.py @@ -1,9 +1,10 @@ -import pytest +from hashlib import sha256 +import pytest from cryptography.hazmat.primitives.asymmetric import ed25519 -from eventsourcingdb import EventCandidate, Container + +from eventsourcingdb import Container, EventCandidate from eventsourcingdb.errors.validation_error import ValidationError -from hashlib import sha256 from ..conftest import TestData @@ -70,7 +71,7 @@ async def test_returns_error_if_hash_verification_fails( written_event = written_events[0] assert written_event.signature is not None - invalid_hash_data = "invalid hash".encode("utf-8") + invalid_hash_data = b"invalid hash" invalid_hash = sha256(invalid_hash_data).hexdigest() written_event.hash = invalid_hash diff --git a/tests/shared/database.py b/tests/shared/database.py index c3b1d46..5a94a15 100644 --- a/tests/shared/database.py +++ b/tests/shared/database.py @@ -1,11 +1,15 @@ +import asyncio import logging import os -import time import uuid +from types import TracebackType +from typing import Self from eventsourcingdb.client import Client from eventsourcingdb.container import Container +logger = logging.getLogger(__name__) + class Database: __create_key = object() @@ -54,9 +58,9 @@ async def create(cls, max_retries=3, retry_delay=2.0) -> 'Database': except OSError as caught_error: error = caught_error retry = True - except Exception as unexpected_error: + except Exception: container.stop() - raise unexpected_error + raise else: retry = False @@ -65,11 +69,11 @@ async def create(cls, max_retries=3, retry_delay=2.0) -> 'Database': container.stop() msg = f'Failed to initialize database container after {max_retries} attempts' raise RuntimeError(f'{msg}: {error}') from error - logging.warning( + logger.warning( 'Container startup attempt %d failed: %s. Retrying in %s seconds...', attempt + 1, error, retry_delay ) - time.sleep(retry_delay) + await asyncio.sleep(retry_delay) container.stop() container = cls._create_container(api_token, "latest") continue @@ -82,9 +86,9 @@ async def create(cls, max_retries=3, retry_delay=2.0) -> 'Database': container, api_token ) - except Exception as client_error: + except Exception: container.stop() - raise client_error + raise return cls(Database.__create_key, with_authorization_client, with_invalid_url_client) @@ -107,14 +111,14 @@ def get_client(self, client_type: str = CLIENT_TYPE_WITH_AUTH) -> Client: raise ValueError(f'Unknown client type: {client_type}') - async def __aenter__(self) -> 'Database': + async def __aenter__(self) -> Self: return self async def __aexit__( self, exc_type: type[BaseException] | None = None, exc_val: BaseException | None = None, - exc_tb: object | None = None + exc_tb: TracebackType | None = None ) -> None: await self.stop() @@ -129,13 +133,13 @@ async def stop(self) -> None: try: await self.__with_authorization_client.__aexit__(None, None, None) except (ConnectionError) as e: - logging.warning("Error closing authorization client: %s", e) + logger.warning("Error closing authorization client: %s", e) if self.__with_invalid_url_client: try: await self.__with_invalid_url_client.__aexit__(None, None, None) except (ConnectionError) as e: - logging.warning("Error closing invalid URL client: %s", e) + logger.warning("Error closing invalid URL client: %s", e) # Then stop the container if (container := getattr(self.__class__, '_Database__container', None)): diff --git a/uv.lock b/uv.lock index 76c73bc..e2023c2 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,6 @@ version = 1 revision = 3 -requires-python = ">=3.11, <=3.13" +requires-python = ">=3.11, <3.15" resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform == 'emscripten'", @@ -532,7 +532,7 @@ dev = [ { name = "pytest-asyncio", specifier = "==1.4.0" }, { name = "pytest-cov", specifier = "==7.1.0" }, { name = "pytest-timeout", specifier = "==2.4.0" }, - { name = "ruff", specifier = "==0.15.21" }, + { name = "ruff", specifier = "==0.16.0" }, { name = "twine", specifier = "==6.2.0" }, ] pandas = [{ name = "pandas", specifier = ">=2.0.0" }] @@ -1311,27 +1311,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.21" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/36/6f65aa9989acdec45d417192d8f4e7921931d8a6cf87ac74bce3eed98a8e/ruff-0.15.21.tar.gz", hash = "sha256:d0cfc841c572283c36548f82664a54ce6565567f1b0d5b4cf2caac693d8b7500", size = 4769401, upload-time = "2026-07-09T20:01:34.005Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/c6/ede15cac6839f3dbce52565c8f5164a8210e669c7bc4decb03e5bdf47d0d/ruff-0.15.21-py3-none-linux_armv6l.whl", hash = "sha256:63ea0e965e5d73c90e95b2434beeafc70820536717f561b32ab6e777cb9bdf5d", size = 10854342, upload-time = "2026-07-09T20:00:53.998Z" }, - { url = "https://files.pythonhosted.org/packages/28/9d/d825b07ee7ea9e2d61df92a860033c94e06e7300d50a1c2653aac27d24fe/ruff-0.15.21-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0f212c5d7d54c01bbfe6dcab02b724a39300f3e34ed7acbe995ccb320a2c58bd", size = 11139539, upload-time = "2026-07-09T20:00:57.809Z" }, - { url = "https://files.pythonhosted.org/packages/f5/de/3b107712e642f063c7a9e0887c427b22cb44097de5aab36c05f2e280670c/ruff-0.15.21-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e6312e41bc96791299614995ea3a977c5857c3b5662b1ecef6755b02b87cb646", size = 10595437, upload-time = "2026-07-09T20:01:00.006Z" }, - { url = "https://files.pythonhosted.org/packages/9a/6f/b4523cc90ba239ede441447a19d0c968846a3012e5a0b0c5b62831a3d5e3/ruff-0.15.21-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01d65b4831c6b2a4ba8ee6faa84049d44d982b7a706e622c4094c509e51673be", size = 10990053, upload-time = "2026-07-09T20:01:02.187Z" }, - { url = "https://files.pythonhosted.org/packages/92/cc/c6a9872a5375f0628875481cf2f66b13d7d865bf3ca2e57f91c7e762d976/ruff-0.15.21-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c5a913a589120ce67933d5d05fd6ddbcc2481c6a054980ee767f7414c72b4fd", size = 10666096, upload-time = "2026-07-09T20:01:04.299Z" }, - { url = "https://files.pythonhosted.org/packages/ab/97/c621f7a17e097f1790fa3af6374138823b330b2d03fc38337945daca212c/ruff-0.15.21-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef04b681d02ad4dc9620f00f83ac5c22f652d0e9a9cfe431d219b16ad5ccc41", size = 11537011, upload-time = "2026-07-09T20:01:06.771Z" }, - { url = "https://files.pythonhosted.org/packages/ea/51/d928727e476e25ccc57c6f449ffd80241a651a973ad949d39cfb2a771d28/ruff-0.15.21-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16d090c0740916594157e75b80d666eab8e78083b39b3b0e1d698f4670a17b86", size = 12347101, upload-time = "2026-07-09T20:01:08.859Z" }, - { url = "https://files.pythonhosted.org/packages/1e/88/8cd62026802b16018ad06931d87997cf795ba2a6239ab659606c87d96bf0/ruff-0.15.21-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a10e74757dd65004d779b73e2f3c5210156d9980b41224d50d2ebcf1db51e67", size = 11572001, upload-time = "2026-07-09T20:01:11.092Z" }, - { url = "https://files.pythonhosted.org/packages/b2/97/f63084cf55444fc110e8cb985ebfcc592af47f597d44453d778cb81bc156/ruff-0.15.21-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bab0905d2f29e0d9fbc3c373ed23db0095edaa3f71f1f4f519ec15134d9e85c8", size = 11549239, upload-time = "2026-07-09T20:01:13.27Z" }, - { url = "https://files.pythonhosted.org/packages/9d/77/f107da4a2874b7715914b03f09ba9c54424de3ff8a1cc5d015d3ee2ce0ac/ruff-0.15.21-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:00eca240af5789fec6fe7df74c088cc1f9644ed83027113468efba7c92b94075", size = 11535340, upload-time = "2026-07-09T20:01:15.206Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e9/601deb322d3303a7bf212b0100ead6f2ee3f6a044d89c30f2f92bf83c731/ruff-0.15.21-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:262ab31557a75141325e32d3357f3597645a7f084e732b6b054dde428ecd9341", size = 10964048, upload-time = "2026-07-09T20:01:17.723Z" }, - { url = "https://files.pythonhosted.org/packages/ea/2e/0f2176d1e99c15192caea19c8c3a0a955246b4cb4de795042eeb616345cd/ruff-0.15.21-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:659c4e7a4212f83306045ec7c5e5a356d16d9a6ef4ae0c7a4d872914fc655d9d", size = 10667055, upload-time = "2026-07-09T20:01:19.73Z" }, - { url = "https://files.pythonhosted.org/packages/48/60/abd74a02e0c4214f12a68becfd30af7165cfdcb0e661ecdc60bbb949c09a/ruff-0.15.21-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9e866eab611a5f959d36df2d10e446973a3610bc42b0c15b31dc27977d59c233", size = 11242043, upload-time = "2026-07-09T20:01:21.947Z" }, - { url = "https://files.pythonhosted.org/packages/b2/c6/583075d8ccabb4b229345edcaf1545eb3d8d6be90f686a479d7e94088bbf/ruff-0.15.21-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e89bc93c0d3803ba870b55c29671bad9dc6d94bb1eb181b056b52eb05b52854f", size = 11648064, upload-time = "2026-07-09T20:01:24.023Z" }, - { url = "https://files.pythonhosted.org/packages/3a/3c/37d0ecb729a7cc2d393ea7dce316fc585680f35d93b8d62139d7d0a3700c/ruff-0.15.21-py3-none-win32.whl", hash = "sha256:01f8d5be84823c172b389e123174f781f9daf86d6c58719d603f941932195cdd", size = 10896555, upload-time = "2026-07-09T20:01:26.941Z" }, - { url = "https://files.pythonhosted.org/packages/c0/b8/e43466b2a6067ce91e669068f6e28d6c719a920f014b070d5c8731725de3/ruff-0.15.21-py3-none-win_amd64.whl", hash = "sha256:d4b8d9a2f0f12b816b50447f6eccb9f4bb01a6b82c86b50fb3b5354b458dc6d3", size = 12038772, upload-time = "2026-07-09T20:01:29.497Z" }, - { url = "https://files.pythonhosted.org/packages/dd/75/e90ab9aeece218a9fc5a5bc3ec97d0ee6bb3c4ff95869463c1de58e29a1c/ruff-0.15.21-py3-none-win_arm64.whl", hash = "sha256:6e83115d4b9377c1cbc13abf0e051f069fab0ef815ea0504a8a008cee24dd0a8", size = 11375265, upload-time = "2026-07-09T20:01:31.772Z" }, +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/94/1e5e4967626faf12fa56999cd6222dff6992ceb086ad7945756baf70c7a7/ruff-0.16.0.tar.gz", hash = "sha256:e460aafd5495ec89efaa6ced2e4a9a581116451e1c88b9d37ef497e0f8e93982", size = 4790557, upload-time = "2026-07-23T19:11:30.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/81/1c8818fee7ce1a04cd7d1b3172e0a8f8e4f1dc4feb7fc390e16daa8af323/ruff-0.16.0-py3-none-linux_armv6l.whl", hash = "sha256:e5115729eb08c585e5121978ba5d5b60caeae394ce21b9fb5e6cd33a1c6c9b1e", size = 10754633, upload-time = "2026-07-23T19:10:46.415Z" }, + { url = "https://files.pythonhosted.org/packages/23/df/beaf59c09d68db84304d555f188b276a77132a5d5b0b67a5c762aa143628/ruff-0.16.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3c954b1d580bfa035b41654f7858cc7e71d5fc3ac5b723dd62bd9133830ed522", size = 10969164, upload-time = "2026-07-23T19:10:50.271Z" }, + { url = "https://files.pythonhosted.org/packages/42/ce/741cd197496a1abbf51352710fd15ed995d2a2be87189c1da26a450d6e83/ruff-0.16.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e01c21d10eb1b29f47b7454e1f4056db9a3f0260c646aa88457c610291db9f81", size = 10488846, upload-time = "2026-07-23T19:10:52.639Z" }, + { url = "https://files.pythonhosted.org/packages/52/2a/a2db8e88cade358f5cdcb05674a917751074109315d014eb6352d9a893f7/ruff-0.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e364e5ed22ed8dc05082fd78e35308618260907ac2d3c1d637b2e682415b6c9", size = 10889729, upload-time = "2026-07-23T19:10:54.89Z" }, + { url = "https://files.pythonhosted.org/packages/42/65/62a771694ebd63029dc953e27dbad40e1588bd4860ff9fe881018fddaa49/ruff-0.16.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d327b8fc113a1d4421a04f3839d3752057c8dd1ee320223a6f3f52d04ada462a", size = 10568275, upload-time = "2026-07-23T19:10:56.993Z" }, + { url = "https://files.pythonhosted.org/packages/3f/e2/ced249fe8af5f086c5c58cc21cc3356d50f32f7401c5df87050c999620a7/ruff-0.16.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b50c55e263103586b3dcf5f73d479eb8cb5fdb6098fec59a62891dab653717", size = 11385112, upload-time = "2026-07-23T19:10:59.615Z" }, + { url = "https://files.pythonhosted.org/packages/87/0b/05154977a8fd69eeb6c103271f55403bfd8711f5c0f8ed07489d95a504e7/ruff-0.16.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ff4a79ce3ec0172f3241943835de1c4cb4e2dcd07f0f8c2d02603dbbbee4b17", size = 12207008, upload-time = "2026-07-23T19:11:02.154Z" }, + { url = "https://files.pythonhosted.org/packages/fb/29/98225831a3a1eab0e02f4acc6ca6559a98611dcc68b6965ff4b7234627c1/ruff-0.16.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e95c448fca1fb2a18372a9440926c5a6ee789639bb975c72e7ae6d0b04218ab4", size = 11650842, upload-time = "2026-07-23T19:11:04.557Z" }, + { url = "https://files.pythonhosted.org/packages/91/66/6bd3cf90500653d55dc0ffc8507aa8300bd49d0214b2e8cb4d3fef2943ba/ruff-0.16.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f11a8d11010301d0a398a2fdef67691feca7294da6aef55e2150e8fa2cd520b", size = 11400718, upload-time = "2026-07-23T19:11:09.233Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a2/a54eb4eae05d66364050a5d3b8a9c5ef88196531b3cbe7109d873f87f819/ruff-0.16.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:48044c678e9cb8698246c99b14aaccfa6601dea7379eb48a6f8f73f7a6d86cd0", size = 11426177, upload-time = "2026-07-23T19:11:11.994Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/16e3eea4b2a478a496919f5e36f17c4559e54620bd3bbac5d6affa068006/ruff-0.16.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7aa0959bad8eb8bef50340154fc9b58678dae31fa4293afa38b44b6e552c0213", size = 10856126, upload-time = "2026-07-23T19:11:14.221Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/252eb8b868a16eec7257c14f504f77537e734b2d69c762e639e588e304a3/ruff-0.16.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28ea2b7df8ebf7f9da6b7d47b230ab48f387c0a29be3b474c4d0740e197bb9af", size = 10571208, upload-time = "2026-07-23T19:11:16.378Z" }, + { url = "https://files.pythonhosted.org/packages/21/09/817a482f542f7570cbb4554b26e896610c7114f539b1d9e2d2145bf6bef6/ruff-0.16.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:33a3dfac8c35f81498dea9181bccc2f4c4bc8f1521a1dd9406e77643e0f0fb09", size = 11063329, upload-time = "2026-07-23T19:11:19.173Z" }, + { url = "https://files.pythonhosted.org/packages/2e/23/9403c180ca1cb9b1f7335f5c3e5305c09d49ea5b345196682a36028bde4a/ruff-0.16.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a5237a0bda500d30d81b8e07a6973a5cbc772864cbf746ae2f4e8a2e01c9f4ed", size = 11489751, upload-time = "2026-07-23T19:11:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/1b2ef7bcde851c78d7f17f1cca13fd6dc695fc4b3d6197941e72cae5b132/ruff-0.16.0-py3-none-win32.whl", hash = "sha256:7fab76fa065c873f41ff744347c6e77bcc3dfec4bcc754dc26b63d23c0f7f5fb", size = 10785885, upload-time = "2026-07-23T19:11:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a3/d5e4ef7a56be3f928ffb90b94c25ba7d3cb9c7fe0736aeaaedf361770712/ruff-0.16.0-py3-none-win_amd64.whl", hash = "sha256:429c117f022bf481fabd9d551e7a3952b24c65e6ef44337ea09d90bebef14472", size = 11923141, upload-time = "2026-07-23T19:11:26.409Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9a/8415f2657cbe200f41a4531ccededf135505a92d4a012229121f885b26f9/ruff-0.16.0-py3-none-win_arm64.whl", hash = "sha256:14296fedcd2705c77ab8235439278bbb38f285cf7da5528b00b3e330c3d4872d", size = 11273407, upload-time = "2026-07-23T19:11:28.705Z" }, ] [[package]]