From 7475a6c73737903dbaa30728f74f30a35c7b1772 Mon Sep 17 00:00:00 2001 From: owenpearson Date: Tue, 2 Dec 2025 14:13:40 +0000 Subject: [PATCH 1/5] ci: add isort to linting and autofix existing imports --- ably/__init__.py | 8 ++++---- ably/http/http.py | 6 +++--- ably/realtime/connection.py | 4 +++- ably/realtime/connectionmanager.py | 19 +++++++++++-------- ably/realtime/realtime.py | 6 +++--- ably/realtime/realtime_channel.py | 6 ++++-- ably/rest/auth.py | 4 ++-- ably/rest/channel.py | 6 +++--- ably/rest/push.py | 8 ++++++-- ably/rest/rest.py | 5 ++--- ably/transport/websockettransport.py | 13 +++++++++---- ably/types/capability.py | 5 ++--- ably/types/channelstate.py | 3 ++- ably/types/connectionstate.py | 2 +- ably/types/device.py | 1 - ably/types/message.py | 2 +- ably/types/mixins.py | 1 - ably/types/options.py | 2 +- ably/util/case.py | 1 - ably/util/crypto.py | 2 +- ably/util/eventemitter.py | 1 + ably/util/exceptions.py | 2 +- ably/util/helper.py | 6 +++--- pyproject.toml | 4 ++-- test/ably/conftest.py | 1 + test/ably/realtime/eventemitter_test.py | 1 + test/ably/realtime/realtimeauth_test.py | 3 ++- test/ably/realtime/realtimechannel_test.py | 8 +++++--- .../realtime/realtimechannel_vcdiff_test.py | 4 ++-- test/ably/realtime/realtimeconnection_test.py | 6 ++++-- test/ably/realtime/realtimeinit_test.py | 4 +++- test/ably/realtime/realtimeresume_test.py | 1 + test/ably/rest/encoders_test.py | 3 +-- test/ably/rest/restauth_test.py | 13 +++++-------- test/ably/rest/restcapability_test.py | 3 +-- test/ably/rest/restchannelhistory_test.py | 4 ++-- test/ably/rest/restchannelpublish_test.py | 6 ++---- test/ably/rest/restchannels_test.py | 1 - test/ably/rest/restchannelstatus_test.py | 2 +- test/ably/rest/restcrypto_test.py | 12 +++++------- test/ably/rest/resthttp_test.py | 3 +-- test/ably/rest/restinit_test.py | 8 +++----- test/ably/rest/restpaginatedresult_test.py | 1 - test/ably/rest/restpresence_test.py | 3 +-- test/ably/rest/restpush_test.py | 14 ++++++++------ test/ably/rest/restrequest_test.py | 3 +-- test/ably/rest/reststats_test.py | 8 +++----- test/ably/rest/resttime_test.py | 3 +-- test/ably/rest/resttoken_test.py | 9 +++------ test/ably/testapp.py | 4 ++-- test/ably/utils.py | 4 ++-- 51 files changed, 126 insertions(+), 123 deletions(-) diff --git a/ably/__init__.py b/ably/__init__.py index d1c12f01..1b30bc3d 100644 --- a/ably/__init__.py +++ b/ably/__init__.py @@ -1,17 +1,17 @@ -from ably.rest.rest import AblyRest +import logging + from ably.realtime.realtime import AblyRealtime from ably.rest.auth import Auth from ably.rest.push import Push +from ably.rest.rest import AblyRest from ably.types.capability import Capability from ably.types.channelsubscription import PushChannelSubscription from ably.types.device import DeviceDetails from ably.types.options import Options, VCDiffDecoder from ably.util.crypto import CipherParams -from ably.util.exceptions import AblyException, AblyAuthException, IncompatibleClientIdException +from ably.util.exceptions import AblyAuthException, AblyException, IncompatibleClientIdException from ably.vcdiff.default_vcdiff_decoder import AblyVCDiffDecoder -import logging - logger = logging.getLogger(__name__) logger.addHandler(logging.NullHandler()) diff --git a/ably/http/http.py b/ably/http/http.py index 45367eef..3d154af3 100644 --- a/ably/http/http.py +++ b/ably/http/http.py @@ -1,17 +1,17 @@ import functools +import json import logging import time -import json from urllib.parse import urljoin import httpx import msgpack -from ably.rest.auth import Auth from ably.http.httputils import HttpUtils +from ably.rest.auth import Auth from ably.transport.defaults import Defaults from ably.util.exceptions import AblyException -from ably.util.helper import is_token_error, extract_url_params +from ably.util.helper import extract_url_params, is_token_error log = logging.getLogger(__name__) diff --git a/ably/realtime/connection.py b/ably/realtime/connection.py index a27d0835..6aa559c5 100644 --- a/ably/realtime/connection.py +++ b/ably/realtime/connection.py @@ -1,12 +1,14 @@ from __future__ import annotations + import functools import logging +from typing import TYPE_CHECKING, Optional + from ably.realtime.connectionmanager import ConnectionManager from ably.types.connectiondetails import ConnectionDetails from ably.types.connectionstate import ConnectionEvent, ConnectionState, ConnectionStateChange from ably.util.eventemitter import EventEmitter from ably.util.exceptions import AblyException -from typing import TYPE_CHECKING, Optional if TYPE_CHECKING: from ably.realtime.realtime import AblyRealtime diff --git a/ably/realtime/connectionmanager.py b/ably/realtime/connectionmanager.py index eb49b2d6..41116a79 100644 --- a/ably/realtime/connectionmanager.py +++ b/ably/realtime/connectionmanager.py @@ -1,19 +1,22 @@ from __future__ import annotations -import logging + import asyncio +import logging +from datetime import datetime +from queue import Queue +from typing import TYPE_CHECKING, Optional + import httpx -from ably.transport.websockettransport import WebSocketTransport, ProtocolMessageAction + from ably.transport.defaults import Defaults +from ably.transport.websockettransport import ProtocolMessageAction, WebSocketTransport +from ably.types.connectiondetails import ConnectionDetails from ably.types.connectionerrors import ConnectionErrors from ably.types.connectionstate import ConnectionEvent, ConnectionState, ConnectionStateChange from ably.types.tokendetails import TokenDetails -from ably.util.exceptions import AblyException, IncompatibleClientIdException from ably.util.eventemitter import EventEmitter -from datetime import datetime -from ably.util.helper import get_random_id, Timer, is_token_error -from typing import Optional, TYPE_CHECKING -from ably.types.connectiondetails import ConnectionDetails -from queue import Queue +from ably.util.exceptions import AblyException, IncompatibleClientIdException +from ably.util.helper import Timer, get_random_id, is_token_error if TYPE_CHECKING: from ably.realtime.realtime import AblyRealtime diff --git a/ably/realtime/realtime.py b/ably/realtime/realtime.py index ea454df1..9b9c4016 100644 --- a/ably/realtime/realtime.py +++ b/ably/realtime/realtime.py @@ -1,11 +1,11 @@ -import logging import asyncio +import logging from typing import Optional -from ably.realtime.realtime_channel import Channels + from ably.realtime.connection import Connection, ConnectionState +from ably.realtime.realtime_channel import Channels from ably.rest.rest import AblyRest - log = logging.getLogger(__name__) diff --git a/ably/realtime/realtime_channel.py b/ably/realtime/realtime_channel.py index e75e8c56..a18e8ebd 100644 --- a/ably/realtime/realtime_channel.py +++ b/ably/realtime/realtime_channel.py @@ -2,9 +2,11 @@ import asyncio import logging -from typing import Optional, TYPE_CHECKING, Dict, Any +from typing import TYPE_CHECKING, Any, Dict, Optional + from ably.realtime.connection import ConnectionState -from ably.rest.channel import Channel, Channels as RestChannels +from ably.rest.channel import Channel +from ably.rest.channel import Channels as RestChannels from ably.transport.websockettransport import ProtocolMessageAction from ably.types.channelstate import ChannelState, ChannelStateChange from ably.types.flags import Flag, has_flag diff --git a/ably/rest/auth.py b/ably/rest/auth.py index a48cc162..a8308d5f 100644 --- a/ably/rest/auth.py +++ b/ably/rest/auth.py @@ -5,15 +5,15 @@ import time import uuid from datetime import timedelta -from typing import Optional, TYPE_CHECKING, Union +from typing import TYPE_CHECKING, Optional, Union import httpx from ably.types.options import Options if TYPE_CHECKING: - from ably.rest.rest import AblyRest from ably.realtime.realtime import AblyRealtime + from ably.rest.rest import AblyRest from ably.types.capability import Capability from ably.types.tokendetails import TokenDetails diff --git a/ably/rest/channel.py b/ably/rest/channel.py index a591fc14..c9ca311e 100644 --- a/ably/rest/channel.py +++ b/ably/rest/channel.py @@ -1,8 +1,8 @@ import base64 -from collections import OrderedDict -import logging import json +import logging import os +from collections import OrderedDict from typing import Iterator from urllib import parse @@ -13,7 +13,7 @@ from ably.types.message import Message, make_message_response_handler from ably.types.presence import Presence from ably.util.crypto import get_cipher -from ably.util.exceptions import catch_all, IncompatibleClientIdException +from ably.util.exceptions import IncompatibleClientIdException, catch_all log = logging.getLogger(__name__) diff --git a/ably/rest/push.py b/ably/rest/push.py index d3cf0e03..11fedc49 100644 --- a/ably/rest/push.py +++ b/ably/rest/push.py @@ -1,8 +1,12 @@ from typing import Optional + from ably.http.paginatedresult import PaginatedResult, format_params +from ably.types.channelsubscription import ( + PushChannelSubscription, + channel_subscriptions_response_processor, + channels_response_processor, +) from ably.types.device import DeviceDetails, device_details_response_processor -from ably.types.channelsubscription import PushChannelSubscription, channel_subscriptions_response_processor -from ably.types.channelsubscription import channels_response_processor class Push: diff --git a/ably/rest/rest.py b/ably/rest/rest.py index a42ba2fd..3b034195 100644 --- a/ably/rest/rest.py +++ b/ably/rest/rest.py @@ -3,15 +3,14 @@ from urllib.parse import urlencode from ably.http.http import Http -from ably.http.paginatedresult import PaginatedResult, HttpPaginatedResponse -from ably.http.paginatedresult import format_params +from ably.http.paginatedresult import HttpPaginatedResponse, PaginatedResult, format_params from ably.rest.auth import Auth from ably.rest.channel import Channels from ably.rest.push import Push -from ably.util.exceptions import AblyException, catch_all from ably.types.options import Options from ably.types.stats import stats_response_processor from ably.types.tokendetails import TokenDetails +from ably.util.exceptions import AblyException, catch_all log = logging.getLogger(__name__) diff --git a/ably/transport/websockettransport.py b/ably/transport/websockettransport.py index d3f39529..0fb7162c 100644 --- a/ably/transport/websockettransport.py +++ b/ably/transport/websockettransport.py @@ -1,22 +1,27 @@ from __future__ import annotations -from typing import TYPE_CHECKING + import asyncio -from enum import IntEnum import json import logging import socket import urllib.parse +from enum import IntEnum +from typing import TYPE_CHECKING + from ably.http.httputils import HttpUtils from ably.types.connectiondetails import ConnectionDetails from ably.util.eventemitter import EventEmitter from ably.util.exceptions import AblyException from ably.util.helper import Timer, unix_time_ms + try: # websockets 15+ preferred imports - from websockets import ClientConnection as WebSocketClientProtocol, connect as ws_connect + from websockets import ClientConnection as WebSocketClientProtocol + from websockets import connect as ws_connect except ImportError: # websockets 14 and earlier fallback - from websockets.client import WebSocketClientProtocol, connect as ws_connect + from websockets.client import WebSocketClientProtocol + from websockets.client import connect as ws_connect from websockets.exceptions import ConnectionClosedOK, WebSocketException diff --git a/ably/types/capability.py b/ably/types/capability.py index 0c35940e..4f931466 100644 --- a/ably/types/capability.py +++ b/ably/types/capability.py @@ -1,8 +1,7 @@ -from collections.abc import MutableMapping -from typing import Optional, Union import json import logging - +from collections.abc import MutableMapping +from typing import Optional, Union log = logging.getLogger(__name__) diff --git a/ably/types/channelstate.py b/ably/types/channelstate.py index 914b5956..dcb68d67 100644 --- a/ably/types/channelstate.py +++ b/ably/types/channelstate.py @@ -1,6 +1,7 @@ from dataclasses import dataclass -from typing import Optional from enum import Enum +from typing import Optional + from ably.util.exceptions import AblyException diff --git a/ably/types/connectionstate.py b/ably/types/connectionstate.py index 3a7fb111..ec958358 100644 --- a/ably/types/connectionstate.py +++ b/ably/types/connectionstate.py @@ -1,5 +1,5 @@ -from enum import Enum from dataclasses import dataclass +from enum import Enum from typing import Optional from ably.util.exceptions import AblyException diff --git a/ably/types/device.py b/ably/types/device.py index 337de002..aa02ac25 100644 --- a/ably/types/device.py +++ b/ably/types/device.py @@ -1,6 +1,5 @@ from ably.util import case - DevicePushTransportType = {'fcm', 'gcm', 'apns', 'web'} DevicePlatform = {'android', 'ios', 'browser'} DeviceFormFactor = {'phone', 'tablet', 'desktop', 'tv', 'watch', 'car', 'embedded', 'other'} diff --git a/ably/types/message.py b/ably/types/message.py index 13fa3c12..7eafcf1b 100644 --- a/ably/types/message.py +++ b/ably/types/message.py @@ -2,8 +2,8 @@ import json import logging +from ably.types.mixins import DeltaExtras, EncodeDataMixin from ably.types.typedbuffer import TypedBuffer -from ably.types.mixins import EncodeDataMixin, DeltaExtras from ably.util.crypto import CipherData from ably.util.exceptions import AblyException diff --git a/ably/types/mixins.py b/ably/types/mixins.py index 31b59f84..4e915f6d 100644 --- a/ably/types/mixins.py +++ b/ably/types/mixins.py @@ -5,7 +5,6 @@ from ably.util.crypto import CipherData from ably.util.exceptions import AblyException - log = logging.getLogger(__name__) ENC_VCDIFF = "vcdiff" diff --git a/ably/types/options.py b/ably/types/options.py index 823b1ae7..3ca1c5ab 100644 --- a/ably/types/options.py +++ b/ably/types/options.py @@ -1,5 +1,5 @@ -import random import logging +import random from abc import ABC, abstractmethod from ably.transport.defaults import Defaults diff --git a/ably/util/case.py b/ably/util/case.py index 3b18c49e..1cfff585 100644 --- a/ably/util/case.py +++ b/ably/util/case.py @@ -1,6 +1,5 @@ import re - first_cap_re = re.compile('(.)([A-Z][a-z]+)') all_cap_re = re.compile('([a-z0-9])([A-Z])') diff --git a/ably/util/crypto.py b/ably/util/crypto.py index 4cc3522e..be89fc34 100644 --- a/ably/util/crypto.py +++ b/ably/util/crypto.py @@ -2,8 +2,8 @@ import logging try: - from Crypto.Cipher import AES from Crypto import Random + from Crypto.Cipher import AES except ImportError: from .nocrypto import AES, Random diff --git a/ably/util/eventemitter.py b/ably/util/eventemitter.py index 4d2bfb41..74f0beb6 100644 --- a/ably/util/eventemitter.py +++ b/ably/util/eventemitter.py @@ -1,5 +1,6 @@ import asyncio import logging + from pyee.asyncio import AsyncIOEventEmitter from ably.util.helper import is_callable_or_coroutine diff --git a/ably/util/exceptions.py b/ably/util/exceptions.py index 6ec73bf0..b096f8dd 100644 --- a/ably/util/exceptions.py +++ b/ably/util/exceptions.py @@ -1,7 +1,7 @@ import functools import logging -import msgpack +import msgpack log = logging.getLogger(__name__) diff --git a/ably/util/helper.py b/ably/util/helper.py index 76ff9e2d..d1df9893 100644 --- a/ably/util/helper.py +++ b/ably/util/helper.py @@ -1,10 +1,10 @@ +import asyncio import inspect import random import string -import asyncio import time -from typing import Callable, Tuple, Dict -from urllib.parse import urlparse, parse_qs +from typing import Callable, Dict, Tuple +from urllib.parse import parse_qs, urlparse def get_random_id(): diff --git a/pyproject.toml b/pyproject.toml index fef1ff57..e6681d71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,8 +87,8 @@ extend-exclude = [ ] [tool.ruff.lint] -# Enable Pyflakes (F), pycodestyle (E, W), and pep8-naming (N) -select = ["E", "W", "F", "N"] +# Enable Pyflakes (F), pycodestyle (E, W), pep8-naming (N), and isort (I) +select = ["E", "W", "F", "N", "I"] ignore = [ "N818", # exception name should end in 'Error' ] diff --git a/test/ably/conftest.py b/test/ably/conftest.py index be61fec1..6b3e529b 100644 --- a/test/ably/conftest.py +++ b/test/ably/conftest.py @@ -1,6 +1,7 @@ import asyncio import pytest + from test.ably.testapp import TestApp diff --git a/test/ably/realtime/eventemitter_test.py b/test/ably/realtime/eventemitter_test.py index 873c2f65..32205b4f 100644 --- a/test/ably/realtime/eventemitter_test.py +++ b/test/ably/realtime/eventemitter_test.py @@ -1,4 +1,5 @@ import asyncio + from ably.realtime.connection import ConnectionState from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase diff --git a/test/ably/realtime/realtimeauth_test.py b/test/ably/realtime/realtimeauth_test.py index 4011e621..6ec53356 100644 --- a/test/ably/realtime/realtimeauth_test.py +++ b/test/ably/realtime/realtimeauth_test.py @@ -1,8 +1,10 @@ import asyncio import json +import urllib.parse import httpx import pytest + from ably.realtime.connection import ConnectionState from ably.transport.websockettransport import ProtocolMessageAction from ably.types.channelstate import ChannelState @@ -10,7 +12,6 @@ from ably.types.tokendetails import TokenDetails from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, random_string -import urllib.parse echo_url = 'https://echo.ably.io' diff --git a/test/ably/realtime/realtimechannel_test.py b/test/ably/realtime/realtimechannel_test.py index a41c46b1..9b9dd15a 100644 --- a/test/ably/realtime/realtimechannel_test.py +++ b/test/ably/realtime/realtimechannel_test.py @@ -1,12 +1,14 @@ import asyncio + import pytest -from ably.realtime.realtime_channel import ChannelState, RealtimeChannel, ChannelOptions + +from ably.realtime.connection import ConnectionState +from ably.realtime.realtime_channel import ChannelOptions, ChannelState, RealtimeChannel from ably.transport.websockettransport import ProtocolMessageAction from ably.types.message import Message +from ably.util.exceptions import AblyException from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, random_string -from ably.realtime.connection import ConnectionState -from ably.util.exceptions import AblyException class TestRealtimeChannel(BaseAsyncTestCase): diff --git a/test/ably/realtime/realtimechannel_vcdiff_test.py b/test/ably/realtime/realtimechannel_vcdiff_test.py index 75b8ce82..086f355c 100644 --- a/test/ably/realtime/realtimechannel_vcdiff_test.py +++ b/test/ably/realtime/realtimechannel_vcdiff_test.py @@ -2,11 +2,11 @@ import json from ably import AblyVCDiffDecoder +from ably.realtime.connection import ConnectionState from ably.realtime.realtime_channel import ChannelOptions +from ably.types.options import VCDiffDecoder from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, WaitableEvent -from ably.realtime.connection import ConnectionState -from ably.types.options import VCDiffDecoder class MockVCDiffDecoder(VCDiffDecoder): diff --git a/test/ably/realtime/realtimeconnection_test.py b/test/ably/realtime/realtimeconnection_test.py index 126c77f0..b4e53ed7 100644 --- a/test/ably/realtime/realtimeconnection_test.py +++ b/test/ably/realtime/realtimeconnection_test.py @@ -1,11 +1,13 @@ import asyncio -from ably.realtime.connection import ConnectionEvent, ConnectionState + import pytest + +from ably.realtime.connection import ConnectionEvent, ConnectionState +from ably.transport.defaults import Defaults from ably.transport.websockettransport import ProtocolMessageAction from ably.util.exceptions import AblyException from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase -from ably.transport.defaults import Defaults class TestRealtimeConnection(BaseAsyncTestCase): diff --git a/test/ably/realtime/realtimeinit_test.py b/test/ably/realtime/realtimeinit_test.py index ef8f99b4..b10c3748 100644 --- a/test/ably/realtime/realtimeinit_test.py +++ b/test/ably/realtime/realtimeinit_test.py @@ -1,7 +1,9 @@ import asyncio -from ably.realtime.connection import ConnectionState + import pytest + from ably import Auth +from ably.realtime.connection import ConnectionState from ably.util.exceptions import AblyAuthException from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase diff --git a/test/ably/realtime/realtimeresume_test.py b/test/ably/realtime/realtimeresume_test.py index 15ec73b2..3ce90963 100644 --- a/test/ably/realtime/realtimeresume_test.py +++ b/test/ably/realtime/realtimeresume_test.py @@ -1,4 +1,5 @@ import asyncio + from ably.realtime.connection import ConnectionState from ably.realtime.realtime_channel import ChannelState from ably.transport.websockettransport import ProtocolMessageAction diff --git a/test/ably/rest/encoders_test.py b/test/ably/rest/encoders_test.py index 6bffba65..df9fb41e 100644 --- a/test/ably/rest/encoders_test.py +++ b/test/ably/rest/encoders_test.py @@ -7,9 +7,8 @@ import msgpack from ably import CipherParams -from ably.util.crypto import get_cipher from ably.types.message import Message - +from ably.util.crypto import get_cipher from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase diff --git a/test/ably/rest/restauth_test.py b/test/ably/rest/restauth_test.py index 656dbf86..ec01b6a3 100644 --- a/test/ably/rest/restauth_test.py +++ b/test/ably/rest/restauth_test.py @@ -1,23 +1,20 @@ +import base64 import logging import sys import time import uuid -import base64 - from urllib.parse import parse_qs + import mock import pytest import respx -from httpx import Response, AsyncClient +from httpx import AsyncClient, Response import ably -from ably import AblyRest -from ably import Auth -from ably import AblyAuthException +from ably import AblyAuthException, AblyRest, Auth from ably.types.tokendetails import TokenDetails - from test.ably.testapp import TestApp -from test.ably.utils import VaryByProtocolTestsMetaclass, dont_vary_protocol, BaseAsyncTestCase +from test.ably.utils import BaseAsyncTestCase, VaryByProtocolTestsMetaclass, dont_vary_protocol if sys.version_info >= (3, 8): from unittest.mock import AsyncMock diff --git a/test/ably/rest/restcapability_test.py b/test/ably/rest/restcapability_test.py index cb74ae8e..b516799e 100644 --- a/test/ably/rest/restcapability_test.py +++ b/test/ably/rest/restcapability_test.py @@ -2,9 +2,8 @@ from ably.types.capability import Capability from ably.util.exceptions import AblyException - from test.ably.testapp import TestApp -from test.ably.utils import VaryByProtocolTestsMetaclass, dont_vary_protocol, BaseAsyncTestCase +from test.ably.utils import BaseAsyncTestCase, VaryByProtocolTestsMetaclass, dont_vary_protocol class TestRestCapability(BaseAsyncTestCase, metaclass=VaryByProtocolTestsMetaclass): diff --git a/test/ably/rest/restchannelhistory_test.py b/test/ably/rest/restchannelhistory_test.py index d1ea1591..50f7fa99 100644 --- a/test/ably/rest/restchannelhistory_test.py +++ b/test/ably/rest/restchannelhistory_test.py @@ -1,12 +1,12 @@ import logging + import pytest import respx from ably import AblyException from ably.http.paginatedresult import PaginatedResult - from test.ably.testapp import TestApp -from test.ably.utils import VaryByProtocolTestsMetaclass, dont_vary_protocol, BaseAsyncTestCase +from test.ably.utils import BaseAsyncTestCase, VaryByProtocolTestsMetaclass, dont_vary_protocol log = logging.getLogger(__name__) diff --git a/test/ably/rest/restchannelpublish_test.py b/test/ably/rest/restchannelpublish_test.py index 89bf86aa..a0783dd6 100644 --- a/test/ably/rest/restchannelpublish_test.py +++ b/test/ably/rest/restchannelpublish_test.py @@ -10,16 +10,14 @@ import msgpack import pytest -from ably import api_version -from ably import AblyException, IncompatibleClientIdException +from ably import AblyException, IncompatibleClientIdException, api_version from ably.rest.auth import Auth from ably.types.message import Message from ably.types.tokendetails import TokenDetails from ably.util import case from test.ably import utils - from test.ably.testapp import TestApp -from test.ably.utils import VaryByProtocolTestsMetaclass, dont_vary_protocol, BaseAsyncTestCase, assert_waiter +from test.ably.utils import BaseAsyncTestCase, VaryByProtocolTestsMetaclass, assert_waiter, dont_vary_protocol log = logging.getLogger(__name__) diff --git a/test/ably/rest/restchannels_test.py b/test/ably/rest/restchannels_test.py index fdeeb125..35f58478 100644 --- a/test/ably/rest/restchannels_test.py +++ b/test/ably/rest/restchannels_test.py @@ -5,7 +5,6 @@ from ably import AblyException from ably.rest.channel import Channel, Channels, Presence from ably.util.crypto import generate_random_key - from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase diff --git a/test/ably/rest/restchannelstatus_test.py b/test/ably/rest/restchannelstatus_test.py index c1c6e5e1..6bc429d4 100644 --- a/test/ably/rest/restchannelstatus_test.py +++ b/test/ably/rest/restchannelstatus_test.py @@ -1,7 +1,7 @@ import logging from test.ably.testapp import TestApp -from test.ably.utils import VaryByProtocolTestsMetaclass, BaseAsyncTestCase +from test.ably.utils import BaseAsyncTestCase, VaryByProtocolTestsMetaclass log = logging.getLogger(__name__) diff --git a/test/ably/rest/restcrypto_test.py b/test/ably/rest/restcrypto_test.py index b6ea577b..996b4267 100644 --- a/test/ably/rest/restcrypto_test.py +++ b/test/ably/rest/restcrypto_test.py @@ -1,19 +1,17 @@ +import base64 import json -import os import logging -import base64 +import os import pytest +from Crypto import Random from ably import AblyException from ably.types.message import Message -from ably.util.crypto import CipherParams, get_cipher, generate_random_key, get_default_params - -from Crypto import Random - +from ably.util.crypto import CipherParams, generate_random_key, get_cipher, get_default_params from test.ably import utils from test.ably.testapp import TestApp -from test.ably.utils import dont_vary_protocol, VaryByProtocolTestsMetaclass, BaseTestCase, BaseAsyncTestCase +from test.ably.utils import BaseAsyncTestCase, BaseTestCase, VaryByProtocolTestsMetaclass, dont_vary_protocol log = logging.getLogger(__name__) diff --git a/test/ably/rest/resthttp_test.py b/test/ably/rest/resthttp_test.py index b6df6be2..fb41c3b8 100644 --- a/test/ably/rest/resthttp_test.py +++ b/test/ably/rest/resthttp_test.py @@ -1,12 +1,11 @@ import base64 import re import time +from urllib.parse import urljoin import httpx import mock import pytest -from urllib.parse import urljoin - import respx from httpx import Response diff --git a/test/ably/rest/restinit_test.py b/test/ably/rest/restinit_test.py index 10dd8282..c9a5a652 100644 --- a/test/ably/rest/restinit_test.py +++ b/test/ably/rest/restinit_test.py @@ -1,14 +1,12 @@ -from mock import patch import pytest from httpx import AsyncClient +from mock import patch -from ably import AblyRest -from ably import AblyException +from ably import AblyException, AblyRest from ably.transport.defaults import Defaults from ably.types.tokendetails import TokenDetails - from test.ably.testapp import TestApp -from test.ably.utils import VaryByProtocolTestsMetaclass, dont_vary_protocol, BaseAsyncTestCase +from test.ably.utils import BaseAsyncTestCase, VaryByProtocolTestsMetaclass, dont_vary_protocol class TestRestInit(BaseAsyncTestCase, metaclass=VaryByProtocolTestsMetaclass): diff --git a/test/ably/rest/restpaginatedresult_test.py b/test/ably/rest/restpaginatedresult_test.py index 1ad693bf..ec57c6be 100644 --- a/test/ably/rest/restpaginatedresult_test.py +++ b/test/ably/rest/restpaginatedresult_test.py @@ -2,7 +2,6 @@ from httpx import Response from ably.http.paginatedresult import PaginatedResult - from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase diff --git a/test/ably/rest/restpresence_test.py b/test/ably/rest/restpresence_test.py index 2c525b02..d5e06b85 100644 --- a/test/ably/rest/restpresence_test.py +++ b/test/ably/rest/restpresence_test.py @@ -5,9 +5,8 @@ from ably.http.paginatedresult import PaginatedResult from ably.types.presence import PresenceMessage - -from test.ably.utils import dont_vary_protocol, VaryByProtocolTestsMetaclass, BaseAsyncTestCase from test.ably.testapp import TestApp +from test.ably.utils import BaseAsyncTestCase, VaryByProtocolTestsMetaclass, dont_vary_protocol class TestPresence(BaseAsyncTestCase, metaclass=VaryByProtocolTestsMetaclass): diff --git a/test/ably/rest/restpush_test.py b/test/ably/rest/restpush_test.py index f4a6a81a..813efb4d 100644 --- a/test/ably/rest/restpush_test.py +++ b/test/ably/rest/restpush_test.py @@ -5,14 +5,16 @@ import pytest -from ably import AblyException, AblyAuthException -from ably import DeviceDetails, PushChannelSubscription +from ably import AblyAuthException, AblyException, DeviceDetails, PushChannelSubscription from ably.http.paginatedresult import PaginatedResult - from test.ably.testapp import TestApp -from test.ably.utils import VaryByProtocolTestsMetaclass, BaseAsyncTestCase -from test.ably.utils import new_dict, random_string, get_random_key - +from test.ably.utils import ( + BaseAsyncTestCase, + VaryByProtocolTestsMetaclass, + get_random_key, + new_dict, + random_string, +) DEVICE_TOKEN = '740f4707bebcf74f9b7c25d48e3358945f6aa01da5ddb387462c7eaf61bb78ad' diff --git a/test/ably/rest/restrequest_test.py b/test/ably/rest/restrequest_test.py index 0f0cd623..98615b4f 100644 --- a/test/ably/rest/restrequest_test.py +++ b/test/ably/rest/restrequest_test.py @@ -6,8 +6,7 @@ from ably.http.paginatedresult import HttpPaginatedResponse from ably.transport.defaults import Defaults from test.ably.testapp import TestApp -from test.ably.utils import BaseAsyncTestCase -from test.ably.utils import VaryByProtocolTestsMetaclass, dont_vary_protocol +from test.ably.utils import BaseAsyncTestCase, VaryByProtocolTestsMetaclass, dont_vary_protocol # RSC19 diff --git a/test/ably/rest/reststats_test.py b/test/ably/rest/reststats_test.py index ca0547b8..e2c63d46 100644 --- a/test/ably/rest/reststats_test.py +++ b/test/ably/rest/reststats_test.py @@ -1,15 +1,13 @@ -from datetime import datetime -from datetime import timedelta import logging +from datetime import datetime, timedelta import pytest +from ably.http.paginatedresult import PaginatedResult from ably.types.stats import Stats from ably.util.exceptions import AblyException -from ably.http.paginatedresult import PaginatedResult - from test.ably.testapp import TestApp -from test.ably.utils import VaryByProtocolTestsMetaclass, dont_vary_protocol, BaseAsyncTestCase +from test.ably.utils import BaseAsyncTestCase, VaryByProtocolTestsMetaclass, dont_vary_protocol log = logging.getLogger(__name__) diff --git a/test/ably/rest/resttime_test.py b/test/ably/rest/resttime_test.py index 6189ebd0..cd19fbf1 100644 --- a/test/ably/rest/resttime_test.py +++ b/test/ably/rest/resttime_test.py @@ -3,9 +3,8 @@ import pytest from ably import AblyException - from test.ably.testapp import TestApp -from test.ably.utils import VaryByProtocolTestsMetaclass, dont_vary_protocol, BaseAsyncTestCase +from test.ably.utils import BaseAsyncTestCase, VaryByProtocolTestsMetaclass, dont_vary_protocol class TestRestTime(BaseAsyncTestCase, metaclass=VaryByProtocolTestsMetaclass): diff --git a/test/ably/rest/resttoken_test.py b/test/ably/rest/resttoken_test.py index 9e74e695..2020b86e 100644 --- a/test/ably/rest/resttoken_test.py +++ b/test/ably/rest/resttoken_test.py @@ -2,17 +2,14 @@ import json import logging -from mock import patch import pytest +from mock import patch -from ably import AblyException -from ably import AblyRest -from ably import Capability +from ably import AblyException, AblyRest, Capability from ably.types.tokendetails import TokenDetails from ably.types.tokenrequest import TokenRequest - from test.ably.testapp import TestApp -from test.ably.utils import VaryByProtocolTestsMetaclass, dont_vary_protocol, BaseAsyncTestCase +from test.ably.utils import BaseAsyncTestCase, VaryByProtocolTestsMetaclass, dont_vary_protocol log = logging.getLogger(__name__) diff --git a/test/ably/testapp.py b/test/ably/testapp.py index 86741f3c..14c54347 100644 --- a/test/ably/testapp.py +++ b/test/ably/testapp.py @@ -1,12 +1,12 @@ import json -import os import logging +import os +from ably.realtime.realtime import AblyRealtime from ably.rest.rest import AblyRest from ably.types.capability import Capability from ably.types.options import Options from ably.util.exceptions import AblyException -from ably.realtime.realtime import AblyRealtime log = logging.getLogger(__name__) diff --git a/test/ably/utils.py b/test/ably/utils.py index 8f383263..5984d570 100644 --- a/test/ably/utils.py +++ b/test/ably/utils.py @@ -6,15 +6,15 @@ import sys import time import unittest -from typing import Callable, Awaitable +from typing import Awaitable, Callable if sys.version_info >= (3, 8): from unittest import IsolatedAsyncioTestCase else: from async_case import IsolatedAsyncioTestCase -import msgpack import mock +import msgpack import respx from httpx import Response From 0f6f8e54798cb90121889a0cc83c87ea9f4bda49 Mon Sep 17 00:00:00 2001 From: owenpearson Date: Tue, 2 Dec 2025 15:31:18 +0000 Subject: [PATCH 2/5] ci: add pyupgrade to linting and fix all violations --- ably/http/http.py | 4 +- ably/http/httputils.py | 2 +- ably/http/paginatedresult.py | 4 +- ably/realtime/connection.py | 8 +-- ably/realtime/connectionmanager.py | 40 +++++++------- ably/realtime/realtime_channel.py | 34 ++++++------ ably/rest/auth.py | 46 ++++++++-------- ably/rest/channel.py | 12 ++-- ably/rest/push.py | 10 ++-- ably/scripts/unasync.py | 2 +- ably/types/authoptions.py | 2 +- ably/types/device.py | 6 +- ably/types/message.py | 2 +- ably/types/mixins.py | 4 +- ably/types/options.py | 2 +- ably/types/presence.py | 4 +- ably/types/tokenrequest.py | 2 +- ably/types/typedbuffer.py | 6 +- ably/util/crypto.py | 7 +-- ably/util/exceptions.py | 6 +- ably/util/helper.py | 2 +- pyproject.toml | 5 +- test/ably/rest/encoders_test.py | 4 +- test/ably/rest/restauth_test.py | 21 +++---- test/ably/rest/restchannelhistory_test.py | 64 +++++++++++----------- test/ably/rest/restchannelpublish_test.py | 12 ++-- test/ably/rest/restchannels_test.py | 2 +- test/ably/rest/restcrypto_test.py | 18 +++--- test/ably/rest/resthttp_test.py | 11 +--- test/ably/rest/restinit_test.py | 17 +++--- test/ably/rest/restpaginatedresult_test.py | 2 +- test/ably/rest/restpresence_test.py | 2 +- test/ably/rest/restrequest_test.py | 4 +- test/ably/rest/resttime_test.py | 4 +- test/ably/rest/resttoken_test.py | 2 +- test/ably/testapp.py | 6 +- test/ably/utils.py | 3 +- 37 files changed, 189 insertions(+), 193 deletions(-) diff --git a/ably/http/http.py b/ably/http/http.py index 3d154af3..bded8494 100644 --- a/ably/http/http.py +++ b/ably/http/http.py @@ -193,9 +193,7 @@ def should_stop_retrying(): # if it's the last try or cumulative timeout is done, we stop retrying return retry_count == len(hosts) - 1 or time_passed > http_max_retry_duration - base_url = "%s://%s:%d" % (self.preferred_scheme, - host, - self.preferred_port) + base_url = f"{self.preferred_scheme}://{host}:{self.preferred_port}" url = urljoin(base_url, path) (clean_url, url_params) = extract_url_params(url) diff --git a/ably/http/httputils.py b/ably/http/httputils.py index b55ae75c..aca46b0f 100644 --- a/ably/http/httputils.py +++ b/ably/http/httputils.py @@ -42,7 +42,7 @@ def default_headers(version=None): version = ably.api_version return { "X-Ably-Version": version, - "Ably-Agent": 'ably-python/%s python/%s' % (ably.lib_version, platform.python_version()) + "Ably-Agent": f'ably-python/{ably.lib_version} python/{platform.python_version()}' } @staticmethod diff --git a/ably/http/paginatedresult.py b/ably/http/paginatedresult.py index 6421251b..a034d9d1 100644 --- a/ably/http/paginatedresult.py +++ b/ably/http/paginatedresult.py @@ -10,7 +10,7 @@ def format_time_param(t): try: - return '%d' % (calendar.timegm(t.utctimetuple()) * 1000) + return f'{calendar.timegm(t.utctimetuple()) * 1000}' except Exception: return str(t) @@ -33,7 +33,7 @@ def format_params(params=None, direction=None, start=None, end=None, limit=None, if limit: if limit > 1000: raise ValueError("The maximum allowed limit is 1000") - params['limit'] = '%d' % limit + params['limit'] = f'{limit}' if 'start' in params and 'end' in params and params['start'] > params['end']: raise ValueError("'end' parameter has to be greater than or equal to 'start'") diff --git a/ably/realtime/connection.py b/ably/realtime/connection.py index 6aa559c5..a810ea3a 100644 --- a/ably/realtime/connection.py +++ b/ably/realtime/connection.py @@ -2,7 +2,7 @@ import functools import logging -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from ably.realtime.connectionmanager import ConnectionManager from ably.types.connectiondetails import ConnectionDetails @@ -41,7 +41,7 @@ class Connection(EventEmitter): # RTN4 def __init__(self, realtime: AblyRealtime): self.__realtime = realtime - self.__error_reason: Optional[AblyException] = None + self.__error_reason: AblyException | None = None self.__state = ConnectionState.CONNECTING if realtime.options.auto_connect else ConnectionState.INITIALIZED self.__connection_manager = ConnectionManager(self.__realtime, self.state) self.__connection_manager.on('connectionstate', self._on_state_update) # RTN4a @@ -104,7 +104,7 @@ def state(self) -> ConnectionState: # RTN25 @property - def error_reason(self) -> Optional[AblyException]: + def error_reason(self) -> AblyException | None: """An object describing the last error which occurred on the channel, if any.""" return self.__error_reason @@ -117,5 +117,5 @@ def connection_manager(self) -> ConnectionManager: return self.__connection_manager @property - def connection_details(self) -> Optional[ConnectionDetails]: + def connection_details(self) -> ConnectionDetails | None: return self.__connection_manager.connection_details diff --git a/ably/realtime/connectionmanager.py b/ably/realtime/connectionmanager.py index 41116a79..2fea5e2a 100644 --- a/ably/realtime/connectionmanager.py +++ b/ably/realtime/connectionmanager.py @@ -4,7 +4,7 @@ import logging from datetime import datetime from queue import Queue -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING import httpx @@ -29,23 +29,23 @@ def __init__(self, realtime: AblyRealtime, initial_state): self.options = realtime.options self.__ably = realtime self.__state: ConnectionState = initial_state - self.__ping_future: Optional[asyncio.Future] = None + self.__ping_future: asyncio.Future | None = None self.__timeout_in_secs: float = self.options.realtime_request_timeout / 1000 - self.transport: Optional[WebSocketTransport] = None - self.__connection_details: Optional[ConnectionDetails] = None - self.connection_id: Optional[str] = None + self.transport: WebSocketTransport | None = None + self.__connection_details: ConnectionDetails | None = None + self.connection_id: str | None = None self.__fail_state = ConnectionState.DISCONNECTED - self.transition_timer: Optional[Timer] = None - self.suspend_timer: Optional[Timer] = None - self.retry_timer: Optional[Timer] = None - self.connect_base_task: Optional[asyncio.Task] = None - self.disconnect_transport_task: Optional[asyncio.Task] = None + self.transition_timer: Timer | None = None + self.suspend_timer: Timer | None = None + self.retry_timer: Timer | None = None + self.connect_base_task: asyncio.Task | None = None + self.disconnect_transport_task: asyncio.Task | None = None self.__fallback_hosts: list[str] = self.options.get_fallback_realtime_hosts() self.queued_messages: Queue = Queue() - self.__error_reason: Optional[AblyException] = None + self.__error_reason: AblyException | None = None super().__init__() - def enact_state_change(self, state: ConnectionState, reason: Optional[AblyException] = None) -> None: + def enact_state_change(self, state: ConnectionState, reason: AblyException | None = None) -> None: current_state = self.__state log.debug(f'ConnectionManager.enact_state_change(): {current_state} -> {state}; reason = {reason}') self.__state = state @@ -146,7 +146,7 @@ async def ping(self) -> float: return round(response_time_ms, 2) def on_connected(self, connection_details: ConnectionDetails, connection_id: str, - reason: Optional[AblyException] = None) -> None: + reason: AblyException | None = None) -> None: self.__fail_state = ConnectionState.DISCONNECTED self.__connection_details = connection_details @@ -236,7 +236,7 @@ async def on_closed(self) -> None: def on_channel_message(self, msg: dict) -> None: self.__ably.channels._on_channel_message(msg) - def on_heartbeat(self, id: Optional[str]) -> None: + def on_heartbeat(self, id: str | None) -> None: if self.__ping_future: # Resolve on heartbeat from ping request. if self.__ping_id == id: @@ -244,7 +244,7 @@ def on_heartbeat(self, id: Optional[str]) -> None: self.__ping_future.set_result(None) self.__ping_future = None - def deactivate_transport(self, reason: Optional[AblyException] = None): + def deactivate_transport(self, reason: AblyException | None = None): self.transport = None self.notify_state(ConnectionState.DISCONNECTED, reason) @@ -278,7 +278,7 @@ def start_connect(self) -> None: self.start_transition_timer(ConnectionState.CONNECTING) self.connect_base_task = asyncio.create_task(self.connect_base()) - async def connect_with_fallback_hosts(self, fallback_hosts: list) -> Optional[Exception]: + async def connect_with_fallback_hosts(self, fallback_hosts: list) -> Exception | None: for host in fallback_hosts: try: if self.check_connection(): @@ -346,8 +346,8 @@ async def on_transport_failed(exception): except asyncio.CancelledError: return - def notify_state(self, state: ConnectionState, reason: Optional[AblyException] = None, - retry_immediately: Optional[bool] = None) -> None: + def notify_state(self, state: ConnectionState, reason: AblyException | None = None, + retry_immediately: bool | None = None) -> None: # RTN15a retry_immediately = (retry_immediately is not False) and ( state == ConnectionState.DISCONNECTED and self.__state == ConnectionState.CONNECTED) @@ -386,7 +386,7 @@ def notify_state(self, state: ConnectionState, reason: Optional[AblyException] = self.fail_queued_messages(reason) self.ably.channels._propagate_connection_interruption(state, reason) - def start_transition_timer(self, state: ConnectionState, fail_state: Optional[ConnectionState] = None) -> None: + def start_transition_timer(self, state: ConnectionState, fail_state: ConnectionState | None = None) -> None: log.debug(f'ConnectionManager.start_transition_timer(): transition state = {state}') if self.transition_timer: @@ -523,5 +523,5 @@ def state(self) -> ConnectionState: return self.__state @property - def connection_details(self) -> Optional[ConnectionDetails]: + def connection_details(self) -> ConnectionDetails | None: return self.__connection_details diff --git a/ably/realtime/realtime_channel.py b/ably/realtime/realtime_channel.py index a18e8ebd..a6d42277 100644 --- a/ably/realtime/realtime_channel.py +++ b/ably/realtime/realtime_channel.py @@ -2,7 +2,7 @@ import asyncio import logging -from typing import TYPE_CHECKING, Any, Dict, Optional +from typing import TYPE_CHECKING, Any from ably.realtime.connection import ConnectionState from ably.rest.channel import Channel @@ -34,7 +34,7 @@ class ChannelOptions: Channel parameters that configure the behavior of the channel. """ - def __init__(self, cipher: Optional[CipherParams] = None, params: Optional[dict] = None): + def __init__(self, cipher: CipherParams | None = None, params: dict | None = None): self.__cipher = cipher self.__params = params # Validate params @@ -47,7 +47,7 @@ def cipher(self): return self.__cipher @property - def params(self) -> Dict[str, str]: + def params(self) -> dict[str, str]: """Get channel parameters""" return self.__params @@ -66,7 +66,7 @@ def __hash__(self): tuple(sorted(self.__params.items())) if self.__params else None, )) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Convert to dictionary representation""" result = {} if self.__cipher is not None: @@ -76,7 +76,7 @@ def to_dict(self) -> Dict[str, Any]: return result @classmethod - def from_dict(cls, options_dict: Dict[str, Any]) -> 'ChannelOptions': + def from_dict(cls, options_dict: dict[str, Any]) -> ChannelOptions: """Create ChannelOptions from dictionary""" if not isinstance(options_dict, dict): raise AblyException("options must be a dictionary", 40000, 400) @@ -112,20 +112,20 @@ class RealtimeChannel(EventEmitter, Channel): Unsubscribe to messages from a channel """ - def __init__(self, realtime: AblyRealtime, name: str, channel_options: Optional[ChannelOptions] = None): + def __init__(self, realtime: AblyRealtime, name: str, channel_options: ChannelOptions | None = None): EventEmitter.__init__(self) self.__name = name self.__realtime = realtime self.__state = ChannelState.INITIALIZED self.__message_emitter = EventEmitter() - self.__state_timer: Optional[Timer] = None + self.__state_timer: Timer | None = None self.__attach_resume = False - self.__attach_serial: Optional[str] = None - self.__channel_serial: Optional[str] = None - self.__retry_timer: Optional[Timer] = None - self.__error_reason: Optional[AblyException] = None + self.__attach_serial: str | None = None + self.__channel_serial: str | None = None + self.__retry_timer: Timer | None = None + self.__error_reason: AblyException | None = None self.__channel_options = channel_options or ChannelOptions() - self.__params: Optional[Dict[str, str]] = None + self.__params: dict[str, str] | None = None # Delta-specific fields for RTL19/RTL20 compliance vcdiff_decoder = self.__realtime.options.vcdiff_decoder if self.__realtime.options.vcdiff_decoder else None @@ -445,7 +445,7 @@ def _request_state(self, state: ChannelState) -> None: self._notify_state(state) self._check_pending_state() - def _notify_state(self, state: ChannelState, reason: Optional[AblyException] = None, + def _notify_state(self, state: ChannelState, reason: AblyException | None = None, resumed: bool = False) -> None: log.debug(f'RealtimeChannel._notify_state(): state = {state}') @@ -565,12 +565,12 @@ def state(self, state: ChannelState) -> None: # RTL24 @property - def error_reason(self) -> Optional[AblyException]: + def error_reason(self) -> AblyException | None: """An AblyException instance describing the last error which occurred on the channel, if any.""" return self.__error_reason @property - def params(self) -> Dict[str, str]: + def params(self) -> dict[str, str]: """Get channel parameters""" return self.__params @@ -605,7 +605,7 @@ class Channels(RestChannels): """ # RTS3 - def get(self, name: str, options: Optional[ChannelOptions] = None) -> RealtimeChannel: + def get(self, name: str, options: ChannelOptions | None = None) -> RealtimeChannel: """Creates a new RealtimeChannel object, or returns the existing channel object. Parameters @@ -668,7 +668,7 @@ def _on_channel_message(self, msg: dict) -> None: channel._on_message(msg) - def _propagate_connection_interruption(self, state: ConnectionState, reason: Optional[AblyException]) -> None: + def _propagate_connection_interruption(self, state: ConnectionState, reason: AblyException | None) -> None: from_channel_states = ( ChannelState.ATTACHING, ChannelState.ATTACHED, diff --git a/ably/rest/auth.py b/ably/rest/auth.py index a8308d5f..2ae771b1 100644 --- a/ably/rest/auth.py +++ b/ably/rest/auth.py @@ -5,7 +5,7 @@ import time import uuid from datetime import timedelta -from typing import TYPE_CHECKING, Optional, Union +from typing import TYPE_CHECKING import httpx @@ -31,7 +31,7 @@ class Method: BASIC = "BASIC" TOKEN = "TOKEN" - def __init__(self, ably: Union[AblyRest, AblyRealtime], options: Options): + def __init__(self, ably: AblyRest | AblyRealtime, options: Options): self.__ably = ably self.__auth_options = options @@ -43,10 +43,10 @@ def __init__(self, ably: Union[AblyRest, AblyRealtime], options: Options): self.__client_id = None self.__client_id_validated: bool = False - self.__basic_credentials: Optional[str] = None - self.__auth_params: Optional[dict] = None - self.__token_details: Optional[TokenDetails] = None - self.__time_offset: Optional[int] = None + self.__basic_credentials: str | None = None + self.__auth_params: dict | None = None + self.__token_details: TokenDetails | None = None + self.__time_offset: int | None = None must_use_token_auth = options.use_token_auth is True must_not_use_token_auth = options.use_token_auth is False @@ -56,7 +56,7 @@ def __init__(self, ably: Union[AblyRest, AblyRealtime], options: Options): # default to using basic auth log.debug("anonymous, using basic auth") self.__auth_mechanism = Auth.Method.BASIC - basic_key = "%s:%s" % (options.key_name, options.key_secret) + basic_key = f"{options.key_name}:{options.key_secret}" basic_key = base64.b64encode(basic_key.encode('utf-8')) self.__basic_credentials = basic_key.decode('ascii') return @@ -151,14 +151,14 @@ def token_details_has_expired(self): return expires < timestamp + token_details.TOKEN_EXPIRY_BUFFER - async def authorize(self, token_params: Optional[dict] = None, auth_options=None): + async def authorize(self, token_params: dict | None = None, auth_options=None): return await self.__authorize_when_necessary(token_params, auth_options, force=True) - async def request_token(self, token_params: Optional[dict] = None, + async def request_token(self, token_params: dict | None = None, # auth_options - key_name: Optional[str] = None, key_secret: Optional[str] = None, auth_callback=None, - auth_url: Optional[str] = None, auth_method: Optional[str] = None, - auth_headers: Optional[dict] = None, auth_params: Optional[dict] = None, + key_name: str | None = None, key_secret: str | None = None, auth_callback=None, + auth_url: str | None = None, auth_method: str | None = None, + auth_headers: dict | None = None, auth_params: dict | None = None, query_time=None): token_params = token_params or {} token_params = dict(self.auth_options.default_token_params, @@ -166,8 +166,8 @@ async def request_token(self, token_params: Optional[dict] = None, key_name = key_name or self.auth_options.key_name key_secret = key_secret or self.auth_options.key_secret - log.debug("Auth callback: %s" % auth_callback) - log.debug("Auth options: %s" % self.auth_options) + log.debug(f"Auth callback: {auth_callback}") + log.debug(f"Auth options: {self.auth_options}") if query_time is None: query_time = self.auth_options.query_time query_time = bool(query_time) @@ -180,7 +180,7 @@ async def request_token(self, token_params: Optional[dict] = None, auth_headers = auth_headers or self.auth_options.auth_headers or {} - log.debug("Token Params: %s" % token_params) + log.debug(f"Token Params: {token_params}") if auth_callback: log.debug("using token auth with authCallback") try: @@ -218,7 +218,7 @@ async def request_token(self, token_params: Optional[dict] = None, elif token_request is None: raise AblyAuthException("Token string was None", 401, 40170) - token_path = "/keys/%s/requestToken" % token_request.key_name + token_path = f"/keys/{token_request.key_name}/requestToken" response = await self.ably.http.post( token_path, @@ -229,11 +229,11 @@ async def request_token(self, token_params: Optional[dict] = None, AblyException.raise_for_response(response) response_dict = response.to_native() - log.debug("Token: %s" % str(response_dict.get("token"))) + log.debug("Token: {}".format(str(response_dict.get("token")))) return TokenDetails.from_dict(response_dict) - async def create_token_request(self, token_params: Optional[dict | str] = None, key_name: Optional[str] = None, - key_secret: Optional[str] = None, query_time=None): + async def create_token_request(self, token_params: dict | str | None = None, key_name: str | None = None, + key_secret: str | None = None, query_time=None): token_params = token_params or {} token_request = {} @@ -349,7 +349,7 @@ def _configure_client_id(self, new_client_id): if original_client_id is not None and original_client_id != '*' and new_client_id != original_client_id: raise IncompatibleClientIdException( "Client ID is immutable once configured for a client. " - "Client ID cannot be changed to '{}'".format(new_client_id), 400, 40102) + f"Client ID cannot be changed to '{new_client_id}'", 400, 40102) self.__client_id_validated = True self.__client_id = new_client_id @@ -369,16 +369,16 @@ async def _get_auth_headers(self): # RSA7e2 if self.client_id: return { - 'Authorization': 'Basic %s' % self.basic_credentials, + 'Authorization': f'Basic {self.basic_credentials}', 'X-Ably-ClientId': base64.b64encode(self.client_id.encode('utf-8')) } return { - 'Authorization': 'Basic %s' % self.basic_credentials, + 'Authorization': f'Basic {self.basic_credentials}', } else: await self.__authorize_when_necessary() return { - 'Authorization': 'Bearer %s' % self.token_credentials, + 'Authorization': f'Bearer {self.token_credentials}', } def _timestamp(self): diff --git a/ably/rest/channel.py b/ably/rest/channel.py index c9ca311e..f925e4dd 100644 --- a/ably/rest/channel.py +++ b/ably/rest/channel.py @@ -22,7 +22,7 @@ class Channel: def __init__(self, ably, name, options): self.__ably = ably self.__name = name - self.__base_path = '/channels/%s/' % parse.quote_plus(name, safe=':') + self.__base_path = '/channels/{}/'.format(parse.quote_plus(name, safe=':')) self.__cipher = None self.options = options self.__presence = Presence(self) @@ -47,7 +47,7 @@ def __publish_request_body(self, messages): if all(message.id is None for message in messages): base_id = base64.b64encode(os.urandom(12)).decode() for serial, message in enumerate(messages): - message.id = '{}:{}'.format(base_id, serial) + message.id = f'{base_id}:{serial}' request_body_list = [] for m in messages: @@ -57,8 +57,8 @@ def __publish_request_body(self, messages): 400, 40012) elif m.client_id is not None and not self.ably.auth.can_assume_client_id(m.client_id): raise IncompatibleClientIdException( - 'Cannot publish with client_id \'{}\' as it is incompatible with the ' - 'current configured client_id \'{}\''.format(m.client_id, self.ably.auth.client_id), + f'Cannot publish with client_id \'{m.client_id}\' as it is incompatible with the ' + f'current configured client_id \'{self.ably.auth.client_id}\'', 400, 40012) if self.cipher: @@ -83,7 +83,7 @@ async def _publish(self, arg, *args, **kwargs): elif isinstance(arg, str): return await self.publish_name_data(arg, *args, **kwargs) else: - raise TypeError('Unexpected type %s' % type(arg)) + raise TypeError(f'Unexpected type {type(arg)}') async def publish_message(self, message, params=None, timeout=None): return await self.publish_messages([message], params, timeout=timeout) @@ -136,7 +136,7 @@ async def publish(self, *args, **kwargs): async def status(self): """Retrieves current channel active status with no. of publishers, subscribers, presence_members etc""" - path = '/channels/%s' % self.name + path = f'/channels/{self.name}' response = await self.ably.http.get(path) obj = response.to_native() return ChannelDetails.from_dict(obj) diff --git a/ably/rest/push.py b/ably/rest/push.py index 11fedc49..f99b2b1d 100644 --- a/ably/rest/push.py +++ b/ably/rest/push.py @@ -47,10 +47,10 @@ async def publish(self, recipient: dict, data: dict, timeout: Optional[float] = - `data`: the data of the notification """ if not isinstance(recipient, dict): - raise TypeError('Unexpected %s recipient, expected a dict' % type(recipient)) + raise TypeError(f'Unexpected {type(recipient)} recipient, expected a dict') if not isinstance(data, dict): - raise TypeError('Unexpected %s data, expected a dict' % type(data)) + raise TypeError(f'Unexpected {type(data)} data, expected a dict') if not recipient: raise ValueError('recipient is empty') @@ -79,7 +79,7 @@ async def get(self, device_id: str): :Parameters: - `device_id`: the id of the device """ - path = '/push/deviceRegistrations/%s' % device_id + path = f'/push/deviceRegistrations/{device_id}' response = await self.ably.http.get(path) obj = response.to_native() return DeviceDetails.from_dict(obj) @@ -103,7 +103,7 @@ async def save(self, device: dict): - `device`: a dictionary with the device information """ device_details = DeviceDetails.factory(device) - path = '/push/deviceRegistrations/%s' % device_details.id + path = f'/push/deviceRegistrations/{device_details.id}' body = device_details.as_dict() response = await self.ably.http.put(path, body=body) obj = response.to_native() @@ -115,7 +115,7 @@ async def remove(self, device_id: str): :Parameters: - `device_id`: the id of the device """ - path = '/push/deviceRegistrations/%s' % device_id + path = f'/push/deviceRegistrations/{device_id}' return await self.ably.http.delete(path) async def remove_where(self, **params): diff --git a/ably/scripts/unasync.py b/ably/scripts/unasync.py index 72126f41..d13e20f2 100644 --- a/ably/scripts/unasync.py +++ b/ably/scripts/unasync.py @@ -72,7 +72,7 @@ def _unasync_file(self, filepath): with open(filepath, "rb") as f: encoding, _ = std_tokenize.detect_encoding(f.readline) - with open(filepath, "rt", encoding=encoding) as f: + with open(filepath, encoding=encoding) as f: tokens = tokenize_rt.src_to_tokens(f.read()) tokens = self._unasync_tokens(tokens) result = tokenize_rt.tokens_to_src(tokens) diff --git a/ably/types/authoptions.py b/ably/types/authoptions.py index f61a57f5..bb15af49 100644 --- a/ably/types/authoptions.py +++ b/ably/types/authoptions.py @@ -34,7 +34,7 @@ def set_key(self, key): self.auth_options['key_name'] = key_name self.auth_options['key_secret'] = key_secret except ValueError: - raise AblyException("key of not len 2 parameters: {0}" + raise AblyException("key of not len 2 parameters: {}" .format(key.split(':')), 401, 40101) diff --git a/ably/types/device.py b/ably/types/device.py index aa02ac25..c2b84ee5 100644 --- a/ably/types/device.py +++ b/ably/types/device.py @@ -16,13 +16,13 @@ def __init__(self, id, client_id=None, form_factor=None, metadata=None, if recipient: transport_type = recipient.get('transportType') if transport_type is not None and transport_type not in DevicePushTransportType: - raise ValueError('unexpected transport type {}'.format(transport_type)) + raise ValueError(f'unexpected transport type {transport_type}') if platform is not None and platform not in DevicePlatform: - raise ValueError('unexpected platform {}'.format(platform)) + raise ValueError(f'unexpected platform {platform}') if form_factor is not None and form_factor not in DeviceFormFactor: - raise ValueError('unexpected form factor {}'.format(form_factor)) + raise ValueError(f'unexpected form factor {form_factor}') self.__id = id self.__client_id = client_id diff --git a/ably/types/message.py b/ably/types/message.py index 7eafcf1b..59dcb736 100644 --- a/ably/types/message.py +++ b/ably/types/message.py @@ -18,7 +18,7 @@ def to_text(value): elif isinstance(value, bytes): return value.decode() else: - raise TypeError("expected string or bytes, not %s" % type(value)) + raise TypeError(f"expected string or bytes, not {type(value)}") class Message(EncodeDataMixin): diff --git a/ably/types/mixins.py b/ably/types/mixins.py index 4e915f6d..29b43f3a 100644 --- a/ably/types/mixins.py +++ b/ably/types/mixins.py @@ -103,7 +103,7 @@ def decode(data, encoding='', cipher=None, context=None): log.error(f'VCDiff decode failed: {e}') raise AblyException('VCDiff decode failure', 40018, 40018) - elif encoding.startswith('%s+' % CipherData.ENCODING_ID): + elif encoding.startswith(f'{CipherData.ENCODING_ID}+'): if not cipher: log.error('Message cannot be decrypted as the channel is ' 'not set up for encryption & decryption') @@ -116,7 +116,7 @@ def decode(data, encoding='', cipher=None, context=None): pass else: log.error('Message cannot be decoded. ' - "Unsupported encoding type: '%s'" % encoding) + f"Unsupported encoding type: '{encoding}'") encoding_list.append(encoding) break diff --git a/ably/types/options.py b/ably/types/options.py index 3ca1c5ab..6990a4b7 100644 --- a/ably/types/options.py +++ b/ably/types/options.py @@ -301,7 +301,7 @@ def __get_rest_hosts(self): # Prepend environment if environment != 'production': - host = '%s-%s' % (environment, host) + host = f'{environment}-{host}' # Fallback hosts fallback_hosts = self.fallback_hosts diff --git a/ably/types/presence.py b/ably/types/presence.py index 6c4f4ca6..c32c634e 100644 --- a/ably/types/presence.py +++ b/ably/types/presence.py @@ -79,7 +79,7 @@ def timestamp(self): @property def member_key(self): if self.connection_id and self.client_id: - return "%s:%s" % (self.connection_id, self.client_id) + return f"{self.connection_id}:{self.client_id}" @property def extras(self): @@ -115,7 +115,7 @@ def from_encoded(obj, cipher=None, context=None): class Presence: def __init__(self, channel): - self.__base_path = '/channels/%s/' % parse.quote_plus(channel.name) + self.__base_path = f'/channels/{parse.quote_plus(channel.name)}/' self.__binary = channel.ably.options.use_binary_protocol self.__http = channel.ably.http self.__cipher = channel.cipher diff --git a/ably/types/tokenrequest.py b/ably/types/tokenrequest.py index d10a5eb3..3998175a 100644 --- a/ably/types/tokenrequest.py +++ b/ably/types/tokenrequest.py @@ -22,7 +22,7 @@ def sign_request(self, key_secret): self.ttl or "", self.capability or "", self.client_id or "", - "%d" % (self.timestamp or 0), + f"{self.timestamp or 0}", self.nonce or "", "", # to get the trailing new line ]]) diff --git a/ably/types/typedbuffer.py b/ably/types/typedbuffer.py index 56adcd88..656f8947 100644 --- a/ably/types/typedbuffer.py +++ b/ably/types/typedbuffer.py @@ -74,7 +74,7 @@ def from_obj(obj): data_type = DataType.INT64 buffer = struct.pack('>q', obj) else: - raise ValueError('Number too large %d' % obj) + raise ValueError(f'Number too large {obj}') elif isinstance(obj, float): data_type = DataType.DOUBLE buffer = struct.pack('>d', obj) @@ -85,7 +85,7 @@ def from_obj(obj): data_type = DataType.JSONOBJECT buffer = json.dumps(obj, separators=(',', ':')).encode('utf-8') else: - raise TypeError('Unexpected object type %s' % type(obj)) + raise TypeError(f'Unexpected object type {type(obj)}') return TypedBuffer(buffer, data_type) @@ -101,4 +101,4 @@ def decode(self): decoder = _decoders.get(self.type) if decoder is not None: return decoder(self.buffer) - raise ValueError('Unsupported data type %s' % self.type) + raise ValueError(f'Unsupported data type {self.type}') diff --git a/ably/util/crypto.py b/ably/util/crypto.py index be89fc34..8d8ddfd9 100644 --- a/ably/util/crypto.py +++ b/ably/util/crypto.py @@ -116,8 +116,7 @@ def iv(self): @property def cipher_type(self): - return ("%s-%s-%s" % (self.__algorithm, self.__key_length, - self.__mode)).lower() + return (f"{self.__algorithm}-{self.__key_length}-{self.__mode}").lower() class CipherData(TypedBuffer): @@ -175,5 +174,5 @@ def validate_cipher_params(cipher_params): if key_length == 128 or key_length == 256: return raise ValueError( - 'Unsupported key length %s for aes-cbc encryption. Encryption key must be 128 or 256 bits' - ' (16 or 32 ASCII characters)' % key_length) + f'Unsupported key length {key_length} for aes-cbc encryption. Encryption key must be 128 or 256 bits' + ' (16 or 32 ASCII characters)') diff --git a/ably/util/exceptions.py b/ably/util/exceptions.py index b096f8dd..6523fdaf 100644 --- a/ably/util/exceptions.py +++ b/ably/util/exceptions.py @@ -20,9 +20,9 @@ def __init__(self, message, status_code, code, cause=None): self.cause = cause def __str__(self): - str = '%s %s %s' % (self.code, self.status_code, self.message) + str = f'{self.code} {self.status_code} {self.message}' if self.cause is not None: - str += ' (cause: %s)' % self.cause + str += f' (cause: {self.cause})' return str @property @@ -77,7 +77,7 @@ def decode_error_response(response): def from_exception(e): if isinstance(e, AblyException): return e - return AblyException("Unexpected exception: %s" % e, 500, 50000) + return AblyException(f"Unexpected exception: {e}", 500, 50000) @staticmethod def from_dict(value: dict): diff --git a/ably/util/helper.py b/ably/util/helper.py index d1df9893..f69a0146 100644 --- a/ably/util/helper.py +++ b/ably/util/helper.py @@ -10,7 +10,7 @@ def get_random_id(): # get random string of letters and digits source = string.ascii_letters + string.digits - random_id = ''.join((random.choice(source) for i in range(8))) + random_id = ''.join(random.choice(source) for i in range(8)) return random_id diff --git a/pyproject.toml b/pyproject.toml index e6681d71..d236755c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,10 +87,11 @@ extend-exclude = [ ] [tool.ruff.lint] -# Enable Pyflakes (F), pycodestyle (E, W), pep8-naming (N), and isort (I) -select = ["E", "W", "F", "N", "I"] +# Enable Pyflakes (F), pycodestyle (E, W), pep8-naming (N), isort (I), and pyupgrade (UP) +select = ["E", "W", "F", "N", "I", "UP"] ignore = [ "N818", # exception name should end in 'Error' + "UP026", # mock -> unittest.mock (need mock package for Python 3.7 AsyncMock support) ] [tool.ruff.lint.per-file-ignores] diff --git a/test/ably/rest/encoders_test.py b/test/ably/rest/encoders_test.py index df9fb41e..001eefbe 100644 --- a/test/ably/rest/encoders_test.py +++ b/test/ably/rest/encoders_test.py @@ -2,8 +2,8 @@ import json import logging import sys +from unittest import mock -import mock import msgpack from ably import CipherParams @@ -15,7 +15,7 @@ if sys.version_info >= (3, 8): from unittest.mock import AsyncMock else: - from mock import AsyncMock + from unittest.mock import AsyncMock log = logging.getLogger(__name__) diff --git a/test/ably/rest/restauth_test.py b/test/ably/rest/restauth_test.py index ec01b6a3..dc5d4fe6 100644 --- a/test/ably/rest/restauth_test.py +++ b/test/ably/rest/restauth_test.py @@ -3,9 +3,9 @@ import sys import time import uuid +from unittest import mock from urllib.parse import parse_qs -import mock import pytest import respx from httpx import AsyncClient, Response @@ -19,7 +19,7 @@ if sys.version_info >= (3, 8): from unittest.mock import AsyncMock else: - from mock import AsyncMock + from unittest.mock import AsyncMock log = logging.getLogger(__name__) @@ -89,7 +89,7 @@ async def test_request_basic_auth_header(self): pass request = get_mock.call_args_list[0][0][0] authorization = request.headers['Authorization'] - assert authorization == 'Basic %s' % base64.b64encode('bar:foo'.encode('ascii')).decode('utf-8') + assert authorization == 'Basic {}'.format(base64.b64encode('bar:foo'.encode('ascii')).decode('utf-8')) # RSA7e2 async def test_request_basic_auth_header_with_client_id(self): @@ -116,7 +116,8 @@ async def test_request_token_auth_header(self): pass request = get_mock.call_args_list[0][0][0] authorization = request.headers['Authorization'] - assert authorization == 'Bearer %s' % base64.b64encode('not_a_real_token'.encode('ascii')).decode('utf-8') + expected_token = base64.b64encode('not_a_real_token'.encode('ascii')).decode('utf-8') + assert authorization == f'Bearer {expected_token}' def test_if_cant_authenticate_via_token(self): with pytest.raises(ValueError): @@ -218,7 +219,7 @@ async def test_authorize_adheres_to_request_token(self): # Authorize may call request_token with some default auth_options. for arg, value in auth_params.items(): - assert auth_called[arg] == value, "%s called with wrong value: %s" % (arg, value) + assert auth_called[arg] == value, f"{arg} called with wrong value: {value}" async def test_with_token_str_https(self): token = await self.ably.auth.authorize() @@ -488,7 +489,7 @@ async def asyncSetUp(self): self.channel = uuid.uuid4().hex tokens = ['a_token', 'another_token'] headers = {'Content-Type': 'application/json'} - self.mocked_api = respx.mock(base_url='https://{}'.format(self.host)) + self.mocked_api = respx.mock(base_url=f'https://{self.host}') self.request_token_route = self.mocked_api.post( "/keys/{}/requestToken".format(self.test_vars["keys"][0]['key_name']), name="request_token_route") @@ -517,7 +518,7 @@ def call_back(request): }, ) - self.publish_attempt_route = self.mocked_api.post("/channels/{}/messages".format(self.channel), + self.publish_attempt_route = self.mocked_api.post(f"/channels/{self.channel}/messages", name="publish_attempt_route") self.publish_attempt_route.side_effect = call_back self.mocked_api.start() @@ -591,8 +592,8 @@ async def asyncSetUp(self): key = self.test_vars["keys"][0]['key_name'] headers = {'Content-Type': 'application/json'} - self.mocked_api = respx.mock(base_url='https://{}'.format(self.host)) - self.request_token_route = self.mocked_api.post("/keys/{}/requestToken".format(key), + self.mocked_api = respx.mock(base_url=f'https://{self.host}') + self.request_token_route = self.mocked_api.post(f"/keys/{key}/requestToken", name="request_token_route") self.request_token_route.return_value = Response( status_code=200, @@ -602,7 +603,7 @@ async def asyncSetUp(self): 'expires': int(time.time() * 1000), # Always expires } ) - self.publish_message_route = self.mocked_api.post("/channels/{}/messages".format(self.channel), + self.publish_message_route = self.mocked_api.post(f"/channels/{self.channel}/messages", name="publish_message_route") self.time_route = self.mocked_api.get("/time", name="time_route") self.time_route.return_value = Response( diff --git a/test/ably/rest/restchannelhistory_test.py b/test/ably/rest/restchannelhistory_test.py index 50f7fa99..c8fe2d49 100644 --- a/test/ably/rest/restchannelhistory_test.py +++ b/test/ably/rest/restchannelhistory_test.py @@ -59,7 +59,7 @@ async def test_channel_history_multi_50_forwards(self): history0 = self.get_channel('persisted:channelhistory_multi_50_f') for i in range(50): - await history0.publish('history%d' % i, str(i)) + await history0.publish(f'history{i}', str(i)) history = await history0.history(direction='forwards') assert history is not None @@ -67,14 +67,14 @@ async def test_channel_history_multi_50_forwards(self): assert len(messages) == 50, "Expected 50 messages" message_contents = {m.name: m for m in messages} - expected_messages = [message_contents['history%d' % i] for i in range(50)] + expected_messages = [message_contents[f'history{i}'] for i in range(50)] assert messages == expected_messages, 'Expect messages in forward order' async def test_channel_history_multi_50_backwards(self): history0 = self.get_channel('persisted:channelhistory_multi_50_b') for i in range(50): - await history0.publish('history%d' % i, str(i)) + await history0.publish(f'history{i}', str(i)) history = await history0.history(direction='backwards') assert history is not None @@ -82,7 +82,7 @@ async def test_channel_history_multi_50_backwards(self): assert 50 == len(messages), "Expected 50 messages" message_contents = {m.name: m for m in messages} - expected_messages = [message_contents['history%d' % i] for i in range(49, -1, -1)] + expected_messages = [message_contents[f'history{i}'] for i in range(49, -1, -1)] assert expected_messages == messages, 'Expect messages in reverse order' def history_mock_url(self, channel_name): @@ -133,7 +133,7 @@ async def test_channel_history_limit_forwards(self): history0 = self.get_channel('persisted:channelhistory_limit_f') for i in range(50): - await history0.publish('history%d' % i, str(i)) + await history0.publish(f'history{i}', str(i)) history = await history0.history(direction='forwards', limit=25) assert history is not None @@ -141,14 +141,14 @@ async def test_channel_history_limit_forwards(self): assert len(messages) == 25, "Expected 25 messages" message_contents = {m.name: m for m in messages} - expected_messages = [message_contents['history%d' % i] for i in range(25)] + expected_messages = [message_contents[f'history{i}'] for i in range(25)] assert messages == expected_messages, 'Expect messages in forward order' async def test_channel_history_limit_backwards(self): history0 = self.get_channel('persisted:channelhistory_limit_b') for i in range(50): - await history0.publish('history%d' % i, str(i)) + await history0.publish(f'history{i}', str(i)) history = await history0.history(direction='backwards', limit=25) assert history is not None @@ -156,24 +156,24 @@ async def test_channel_history_limit_backwards(self): assert len(messages) == 25, "Expected 25 messages" message_contents = {m.name: m for m in messages} - expected_messages = [message_contents['history%d' % i] for i in range(49, 24, -1)] + expected_messages = [message_contents[f'history{i}'] for i in range(49, 24, -1)] assert messages == expected_messages, 'Expect messages in forward order' async def test_channel_history_time_forwards(self): history0 = self.get_channel('persisted:channelhistory_time_f') for i in range(20): - await history0.publish('history%d' % i, str(i)) + await history0.publish(f'history{i}', str(i)) interval_start = await self.ably.time() for i in range(20, 40): - await history0.publish('history%d' % i, str(i)) + await history0.publish(f'history{i}', str(i)) interval_end = await self.ably.time() for i in range(40, 60): - await history0.publish('history%d' % i, str(i)) + await history0.publish(f'history{i}', str(i)) history = await history0.history(direction='forwards', start=interval_start, end=interval_end) @@ -182,24 +182,24 @@ async def test_channel_history_time_forwards(self): assert 20 == len(messages) message_contents = {m.name: m for m in messages} - expected_messages = [message_contents['history%d' % i] for i in range(20, 40)] + expected_messages = [message_contents[f'history{i}'] for i in range(20, 40)] assert expected_messages == messages, 'Expect messages in forward order' async def test_channel_history_time_backwards(self): history0 = self.get_channel('persisted:channelhistory_time_b') for i in range(20): - await history0.publish('history%d' % i, str(i)) + await history0.publish(f'history{i}', str(i)) interval_start = await self.ably.time() for i in range(20, 40): - await history0.publish('history%d' % i, str(i)) + await history0.publish(f'history{i}', str(i)) interval_end = await self.ably.time() for i in range(40, 60): - await history0.publish('history%d' % i, str(i)) + await history0.publish(f'history{i}', str(i)) history = await history0.history(direction='backwards', start=interval_start, end=interval_end) @@ -208,14 +208,14 @@ async def test_channel_history_time_backwards(self): assert 20 == len(messages) message_contents = {m.name: m for m in messages} - expected_messages = [message_contents['history%d' % i] for i in range(39, 19, -1)] + expected_messages = [message_contents[f'history{i}'] for i in range(39, 19, -1)] assert expected_messages, messages == 'Expect messages in reverse order' async def test_channel_history_paginate_forwards(self): history0 = self.get_channel('persisted:channelhistory_paginate_f') for i in range(50): - await history0.publish('history%d' % i, str(i)) + await history0.publish(f'history{i}', str(i)) history = await history0.history(direction='forwards', limit=10) messages = history.items @@ -223,7 +223,7 @@ async def test_channel_history_paginate_forwards(self): assert 10 == len(messages) message_contents = {m.name: m for m in messages} - expected_messages = [message_contents['history%d' % i] for i in range(0, 10)] + expected_messages = [message_contents[f'history{i}'] for i in range(0, 10)] assert expected_messages == messages, 'Expected 10 messages' history = await history.next() @@ -231,7 +231,7 @@ async def test_channel_history_paginate_forwards(self): assert 10 == len(messages) message_contents = {m.name: m for m in messages} - expected_messages = [message_contents['history%d' % i] for i in range(10, 20)] + expected_messages = [message_contents[f'history{i}'] for i in range(10, 20)] assert expected_messages == messages, 'Expected 10 messages' history = await history.next() @@ -239,21 +239,21 @@ async def test_channel_history_paginate_forwards(self): assert 10 == len(messages) message_contents = {m.name: m for m in messages} - expected_messages = [message_contents['history%d' % i] for i in range(20, 30)] + expected_messages = [message_contents[f'history{i}'] for i in range(20, 30)] assert expected_messages == messages, 'Expected 10 messages' async def test_channel_history_paginate_backwards(self): history0 = self.get_channel('persisted:channelhistory_paginate_b') for i in range(50): - await history0.publish('history%d' % i, str(i)) + await history0.publish(f'history{i}', str(i)) history = await history0.history(direction='backwards', limit=10) messages = history.items assert 10 == len(messages) message_contents = {m.name: m for m in messages} - expected_messages = [message_contents['history%d' % i] for i in range(49, 39, -1)] + expected_messages = [message_contents[f'history{i}'] for i in range(49, 39, -1)] assert expected_messages == messages, 'Expected 10 messages' history = await history.next() @@ -261,7 +261,7 @@ async def test_channel_history_paginate_backwards(self): assert 10 == len(messages) message_contents = {m.name: m for m in messages} - expected_messages = [message_contents['history%d' % i] for i in range(39, 29, -1)] + expected_messages = [message_contents[f'history{i}'] for i in range(39, 29, -1)] assert expected_messages == messages, 'Expected 10 messages' history = await history.next() @@ -269,20 +269,20 @@ async def test_channel_history_paginate_backwards(self): assert 10 == len(messages) message_contents = {m.name: m for m in messages} - expected_messages = [message_contents['history%d' % i] for i in range(29, 19, -1)] + expected_messages = [message_contents[f'history{i}'] for i in range(29, 19, -1)] assert expected_messages == messages, 'Expected 10 messages' async def test_channel_history_paginate_forwards_first(self): history0 = self.get_channel('persisted:channelhistory_paginate_first_f') for i in range(50): - await history0.publish('history%d' % i, str(i)) + await history0.publish(f'history{i}', str(i)) history = await history0.history(direction='forwards', limit=10) messages = history.items assert 10 == len(messages) message_contents = {m.name: m for m in messages} - expected_messages = [message_contents['history%d' % i] for i in range(0, 10)] + expected_messages = [message_contents[f'history{i}'] for i in range(0, 10)] assert expected_messages == messages, 'Expected 10 messages' history = await history.next() @@ -290,7 +290,7 @@ async def test_channel_history_paginate_forwards_first(self): assert 10 == len(messages) message_contents = {m.name: m for m in messages} - expected_messages = [message_contents['history%d' % i] for i in range(10, 20)] + expected_messages = [message_contents[f'history{i}'] for i in range(10, 20)] assert expected_messages == messages, 'Expected 10 messages' history = await history.first() @@ -298,21 +298,21 @@ async def test_channel_history_paginate_forwards_first(self): assert 10 == len(messages) message_contents = {m.name: m for m in messages} - expected_messages = [message_contents['history%d' % i] for i in range(0, 10)] + expected_messages = [message_contents[f'history{i}'] for i in range(0, 10)] assert expected_messages == messages, 'Expected 10 messages' async def test_channel_history_paginate_backwards_rel_first(self): history0 = self.get_channel('persisted:channelhistory_paginate_first_b') for i in range(50): - await history0.publish('history%d' % i, str(i)) + await history0.publish(f'history{i}', str(i)) history = await history0.history(direction='backwards', limit=10) messages = history.items assert 10 == len(messages) message_contents = {m.name: m for m in messages} - expected_messages = [message_contents['history%d' % i] for i in range(49, 39, -1)] + expected_messages = [message_contents[f'history{i}'] for i in range(49, 39, -1)] assert expected_messages == messages, 'Expected 10 messages' history = await history.next() @@ -320,7 +320,7 @@ async def test_channel_history_paginate_backwards_rel_first(self): assert 10 == len(messages) message_contents = {m.name: m for m in messages} - expected_messages = [message_contents['history%d' % i] for i in range(39, 29, -1)] + expected_messages = [message_contents[f'history{i}'] for i in range(39, 29, -1)] assert expected_messages == messages, 'Expected 10 messages' history = await history.first() @@ -328,5 +328,5 @@ async def test_channel_history_paginate_backwards_rel_first(self): assert 10 == len(messages) message_contents = {m.name: m for m in messages} - expected_messages = [message_contents['history%d' % i] for i in range(49, 39, -1)] + expected_messages = [message_contents[f'history{i}'] for i in range(49, 39, -1)] assert expected_messages == messages, 'Expected 10 messages' diff --git a/test/ably/rest/restchannelpublish_test.py b/test/ably/rest/restchannelpublish_test.py index a0783dd6..6359649e 100644 --- a/test/ably/rest/restchannelpublish_test.py +++ b/test/ably/rest/restchannelpublish_test.py @@ -4,9 +4,9 @@ import logging import os import uuid +from unittest import mock import httpx -import mock import msgpack import pytest @@ -57,13 +57,13 @@ async def test_publish_various_datatypes_text(self): assert len(messages) == 4, "Expected 4 messages" message_contents = dict((m.name, m.data) for m in messages) - log.debug("message_contents: %s" % str(message_contents)) + log.debug(f"message_contents: {str(message_contents)}") assert message_contents["publish0"] == "This is a string message payload", \ "Expect publish0 to be expected String)" assert message_contents["publish1"] == b"This is a byte[] message payload", \ - "Expect publish1 to be expected byte[]. Actual: %s" % str(message_contents['publish1']) + "Expect publish1 to be expected byte[]. Actual: {}".format(str(message_contents['publish1'])) assert message_contents["publish2"] == {"test": "This is a JSONObject message payload"}, \ "Expect publish2 to be expected JSONObject" @@ -82,7 +82,7 @@ async def test_publish_message_list(self): channel = self.ably.channels[ self.get_channel_name('persisted:message_list_channel')] - expected_messages = [Message("name-{}".format(i), str(i)) for i in range(3)] + expected_messages = [Message(f"name-{i}", str(i)) for i in range(3)] await channel.publish(messages=expected_messages) @@ -101,7 +101,7 @@ async def test_message_list_generate_one_request(self): channel = self.ably.channels[ self.get_channel_name('persisted:message_list_channel_one_request')] - expected_messages = [Message("name-{}".format(i), str(i)) for i in range(3)] + expected_messages = [Message(f"name-{i}", str(i)) for i in range(3)] with mock.patch('ably.rest.rest.Http.post', wraps=channel.ably.http.post) as post_mock: @@ -373,7 +373,7 @@ async def test_interoperability(self): name = self.get_channel_name('persisted:interoperability_channel') channel = self.ably.channels[name] - url = 'https://%s/channels/%s/messages' % (self.test_vars["host"], name) + url = 'https://{}/channels/{}/messages'.format(self.test_vars["host"], name) key = self.test_vars['keys'][0] auth = (key['key_name'], key['key_secret']) diff --git a/test/ably/rest/restchannels_test.py b/test/ably/rest/restchannels_test.py index 35f58478..c6e1d058 100644 --- a/test/ably/rest/restchannels_test.py +++ b/test/ably/rest/restchannels_test.py @@ -61,7 +61,7 @@ def test_channels_in(self): assert new_channel_2 in self.ably.channels def test_channels_iteration(self): - channel_names = ['channel_{}'.format(i) for i in range(5)] + channel_names = [f'channel_{i}' for i in range(5)] [self.ably.channels.get(name) for name in channel_names] assert isinstance(self.ably.channels, Iterable) diff --git a/test/ably/rest/restcrypto_test.py b/test/ably/rest/restcrypto_test.py index 996b4267..6b31f0c3 100644 --- a/test/ably/rest/restcrypto_test.py +++ b/test/ably/rest/restcrypto_test.py @@ -43,8 +43,8 @@ def test_cbc_channel_cipher(self): b'\x28\x4c\xe4\x8d\x4b\xdc\x9d\x42' b'\x8a\x77\x6b\x53\x2d\xc7\xb5\xc0') - log.debug("KEY_LEN: %d" % len(key)) - log.debug("IV_LEN: %d" % len(iv)) + log.debug(f"KEY_LEN: {len(key)}") + log.debug(f"IV_LEN: {len(iv)}") cipher = get_cipher({'key': key, 'iv': iv}) plaintext = b"The quick brown fox" @@ -75,13 +75,13 @@ async def test_crypto_publish(self): assert 4 == len(messages), "Expected 4 messages" message_contents = dict((m.name, m.data) for m in messages) - log.debug("message_contents: %s" % str(message_contents)) + log.debug(f"message_contents: {str(message_contents)}") assert "This is a string message payload" == message_contents["publish3"],\ "Expect publish3 to be expected String)" assert b"This is a byte[] message payload" == message_contents["publish4"],\ - "Expect publish4 to be expected byte[]. Actual: %s" % str(message_contents['publish4']) + "Expect publish4 to be expected byte[]. Actual: {}".format(str(message_contents['publish4'])) assert {"test": "This is a JSONObject message payload"} == message_contents["publish5"],\ "Expect publish5 to be expected JSONObject" @@ -108,13 +108,13 @@ async def test_crypto_publish_256(self): assert 4 == len(messages), "Expected 4 messages" message_contents = dict((m.name, m.data) for m in messages) - log.debug("message_contents: %s" % str(message_contents)) + log.debug(f"message_contents: {str(message_contents)}") assert "This is a string message payload" == message_contents["publish3"],\ "Expect publish3 to be expected String)" assert b"This is a byte[] message payload" == message_contents["publish4"],\ - "Expect publish4 to be expected byte[]. Actual: %s" % str(message_contents['publish4']) + "Expect publish4 to be expected byte[]. Actual: {}".format(str(message_contents['publish4'])) assert {"test": "This is a JSONObject message payload"} == message_contents["publish5"],\ "Expect publish5 to be expected JSONObject" @@ -157,13 +157,13 @@ async def test_crypto_send_unencrypted(self): assert 4 == len(messages), "Expected 4 messages" message_contents = dict((m.name, m.data) for m in messages) - log.debug("message_contents: %s" % str(message_contents)) + log.debug(f"message_contents: {str(message_contents)}") assert "This is a string message payload" == message_contents["publish3"],\ "Expect publish3 to be expected String" assert b"This is a byte[] message payload" == message_contents["publish4"],\ - "Expect publish4 to be expected byte[]. Actual: %s" % str(message_contents['publish4']) + "Expect publish4 to be expected byte[]. Actual: {}".format(str(message_contents['publish4'])) assert {"test": "This is a JSONObject message payload"} == message_contents["publish5"],\ "Expect publish5 to be expected JSONObject" @@ -204,7 +204,7 @@ class AbstractTestCryptoWithFixture: @classmethod def setUpClass(cls): resources_path = os.path.join(utils.get_submodule_dir(__file__), 'test-resources', cls.fixture_file) - with open(resources_path, 'r') as f: + with open(resources_path) as f: cls.fixture = json.loads(f.read()) cls.params = { 'secret_key': base64.b64decode(cls.fixture['key'].encode('ascii')), diff --git a/test/ably/rest/resthttp_test.py b/test/ably/rest/resthttp_test.py index fb41c3b8..ba101c21 100644 --- a/test/ably/rest/resthttp_test.py +++ b/test/ably/rest/resthttp_test.py @@ -1,10 +1,10 @@ import base64 import re import time +from unittest import mock from urllib.parse import urljoin import httpx -import mock import pytest import respx from httpx import Response @@ -52,9 +52,7 @@ async def test_host_fallback(self): ably = AblyRest(token="foo") def make_url(host): - base_url = "%s://%s:%d" % (ably.http.preferred_scheme, - host, - ably.http.preferred_port) + base_url = f"{ably.http.preferred_scheme}://{host}:{ably.http.preferred_port}" return urljoin(base_url, '/') with mock.patch('httpx.Request', wraps=httpx.Request) as request_mock: @@ -133,10 +131,7 @@ async def test_no_retry_if_not_500_to_599_http_code(self): default_host = Options().get_rest_host() ably = AblyRest(token="foo") - default_url = "%s://%s:%d/" % ( - ably.http.preferred_scheme, - default_host, - ably.http.preferred_port) + default_url = f"{ably.http.preferred_scheme}://{default_host}:{ably.http.preferred_port}/" mock_response = httpx.Response(600, json={'message': "", 'status_code': 600, 'code': 50500}) diff --git a/test/ably/rest/restinit_test.py b/test/ably/rest/restinit_test.py index c9a5a652..86aae3b6 100644 --- a/test/ably/rest/restinit_test.py +++ b/test/ably/rest/restinit_test.py @@ -1,6 +1,7 @@ +from unittest.mock import patch + import pytest from httpx import AsyncClient -from mock import patch from ably import AblyException, AblyRest from ably.transport.defaults import Defaults @@ -74,12 +75,12 @@ def test_rest_host_and_environment(self): # environment: production ably = AblyRest(token='foo', environment="production") host = ably.options.get_rest_host() - assert "rest.ably.io" == host, "Unexpected host mismatch %s" % host + assert "rest.ably.io" == host, f"Unexpected host mismatch {host}" # environment: other ably = AblyRest(token='foo', environment="sandbox") host = ably.options.get_rest_host() - assert "sandbox-rest.ably.io" == host, "Unexpected host mismatch %s" % host + assert "sandbox-rest.ably.io" == host, f"Unexpected host mismatch {host}" # both, as per #TO3k2 with pytest.raises(ValueError): @@ -124,19 +125,19 @@ def test_specified_realtime_host(self): def test_specified_port(self): ably = AblyRest(token='foo', port=9998, tls_port=9999) assert 9999 == Defaults.get_port(ably.options),\ - "Unexpected port mismatch. Expected: 9999. Actual: %d" % ably.options.tls_port + f"Unexpected port mismatch. Expected: 9999. Actual: {ably.options.tls_port}" @dont_vary_protocol def test_specified_non_tls_port(self): ably = AblyRest(token='foo', port=9998, tls=False) assert 9998 == Defaults.get_port(ably.options),\ - "Unexpected port mismatch. Expected: 9999. Actual: %d" % ably.options.tls_port + f"Unexpected port mismatch. Expected: 9999. Actual: {ably.options.tls_port}" @dont_vary_protocol def test_specified_tls_port(self): ably = AblyRest(token='foo', tls_port=9999, tls=True) assert 9999 == Defaults.get_port(ably.options),\ - "Unexpected port mismatch. Expected: 9999. Actual: %d" % ably.options.tls_port + f"Unexpected port mismatch. Expected: 9999. Actual: {ably.options.tls_port}" @dont_vary_protocol def test_tls_defaults_to_true(self): @@ -180,13 +181,13 @@ async def test_query_time_param(self): @dont_vary_protocol def test_requests_over_https_production(self): ably = AblyRest(token='token') - assert 'https://rest.ably.io' == '{0}://{1}'.format(ably.http.preferred_scheme, ably.http.preferred_host) + assert 'https://rest.ably.io' == f'{ably.http.preferred_scheme}://{ably.http.preferred_host}' assert ably.http.preferred_port == 443 @dont_vary_protocol def test_requests_over_http_production(self): ably = AblyRest(token='token', tls=False) - assert 'http://rest.ably.io' == '{0}://{1}'.format(ably.http.preferred_scheme, ably.http.preferred_host) + assert 'http://rest.ably.io' == f'{ably.http.preferred_scheme}://{ably.http.preferred_host}' assert ably.http.preferred_port == 80 @dont_vary_protocol diff --git a/test/ably/rest/restpaginatedresult_test.py b/test/ably/rest/restpaginatedresult_test.py index ec57c6be..67ca9c59 100644 --- a/test/ably/rest/restpaginatedresult_test.py +++ b/test/ably/rest/restpaginatedresult_test.py @@ -15,7 +15,7 @@ def callback(request): return Response( status_code=status, headers=headers, - content='[{"page": %i}]' % int(res) + content=f'[{{"page": {int(res)}}}]' ) return Response( diff --git a/test/ably/rest/restpresence_test.py b/test/ably/rest/restpresence_test.py index d5e06b85..626be969 100644 --- a/test/ably/rest/restpresence_test.py +++ b/test/ably/rest/restpresence_test.py @@ -69,7 +69,7 @@ async def test_presence_message_has_correct_member_key(self): presence_page = await self.channel.presence.get() member = presence_page.items[0] - assert member.member_key == "%s:%s" % (member.connection_id, member.client_id) + assert member.member_key == f"{member.connection_id}:{member.client_id}" def presence_mock_url(self): kwargs = { diff --git a/test/ably/rest/restrequest_test.py b/test/ably/rest/restrequest_test.py index 98615b4f..51cbae7b 100644 --- a/test/ably/rest/restrequest_test.py +++ b/test/ably/rest/restrequest_test.py @@ -18,9 +18,9 @@ async def asyncSetUp(self): # Populate the channel (using the new api) self.channel = self.get_channel_name() - self.path = '/channels/%s/messages' % self.channel + self.path = f'/channels/{self.channel}/messages' for i in range(20): - body = {'name': 'event%s' % i, 'data': 'lorem ipsum %s' % i} + body = {'name': f'event{i}', 'data': f'lorem ipsum {i}'} await self.ably.request('POST', self.path, body=body, version=Defaults.protocol_version) async def asyncTearDown(self): diff --git a/test/ably/rest/resttime_test.py b/test/ably/rest/resttime_test.py index cd19fbf1..ff64a029 100644 --- a/test/ably/rest/resttime_test.py +++ b/test/ably/rest/resttime_test.py @@ -24,14 +24,14 @@ async def test_time_accuracy(self): actual_time = time.time() * 1000.0 seconds = 10 - assert abs(actual_time - reported_time) < seconds * 1000, "Time is not within %s seconds" % seconds + assert abs(actual_time - reported_time) < seconds * 1000, f"Time is not within {seconds} seconds" async def test_time_without_key_or_token(self): reported_time = await self.ably.time() actual_time = time.time() * 1000.0 seconds = 10 - assert abs(actual_time - reported_time) < seconds * 1000, "Time is not within %s seconds" % seconds + assert abs(actual_time - reported_time) < seconds * 1000, f"Time is not within {seconds} seconds" @dont_vary_protocol async def test_time_fails_without_valid_host(self): diff --git a/test/ably/rest/resttoken_test.py b/test/ably/rest/resttoken_test.py index 2020b86e..727d81ee 100644 --- a/test/ably/rest/resttoken_test.py +++ b/test/ably/rest/resttoken_test.py @@ -1,9 +1,9 @@ import datetime import json import logging +from unittest.mock import patch import pytest -from mock import patch from ably import AblyException, AblyRest, Capability from ably.types.tokendetails import TokenDetails diff --git a/test/ably/testapp.py b/test/ably/testapp.py index 14c54347..a5efb06c 100644 --- a/test/ably/testapp.py +++ b/test/ably/testapp.py @@ -10,7 +10,7 @@ log = logging.getLogger(__name__) -with open(os.path.dirname(__file__) + '/../assets/testAppSpec.json', 'r') as f: +with open(os.path.dirname(__file__) + '/../assets/testAppSpec.json') as f: app_spec_local = json.loads(f.read()) tls = (os.environ.get('ABLY_TLS') or "true").lower() == "true" @@ -56,9 +56,9 @@ async def get_test_vars(): "environment": environment, "realtime_host": realtime_host, "keys": [{ - "key_name": "%s.%s" % (app_id, k.get("id", "")), + "key_name": "{}.{}".format(app_id, k.get("id", "")), "key_secret": k.get("value", ""), - "key_str": "%s.%s:%s" % (app_id, k.get("id", ""), k.get("value", "")), + "key_str": "{}.{}:{}".format(app_id, k.get("id", ""), k.get("value", "")), "capability": Capability(json.loads(k.get("capability", "{}"))), } for k in app_spec.get("keys", [])] } diff --git a/test/ably/utils.py b/test/ably/utils.py index 5984d570..4ce16886 100644 --- a/test/ably/utils.py +++ b/test/ably/utils.py @@ -13,7 +13,8 @@ else: from async_case import IsolatedAsyncioTestCase -import mock +from unittest import mock + import msgpack import respx from httpx import Response From 387daa4a1b4cb565bf8cb4f65a3e055c68a3f552 Mon Sep 17 00:00:00 2001 From: owenpearson Date: Tue, 2 Dec 2025 15:44:07 +0000 Subject: [PATCH 3/5] ci: add bugbear to linting and fix all violations --- ably/http/http.py | 2 +- ably/realtime/connectionmanager.py | 4 ++-- ably/rest/auth.py | 4 ++-- ably/rest/rest.py | 4 +--- ably/transport/websockettransport.py | 2 +- ably/types/authoptions.py | 2 +- ably/types/mixins.py | 2 +- ably/util/exceptions.py | 6 +++--- pyproject.toml | 4 ++-- test/ably/rest/restchannelpublish_test.py | 4 ++-- test/ably/rest/restpush_test.py | 4 ++-- test/ably/utils.py | 2 +- 12 files changed, 19 insertions(+), 21 deletions(-) diff --git a/ably/http/http.py b/ably/http/http.py index bded8494..0792df99 100644 --- a/ably/http/http.py +++ b/ably/http/http.py @@ -188,7 +188,7 @@ async def make_request(self, method, path, version=None, headers=None, body=None hosts = self.get_rest_hosts() for retry_count, host in enumerate(hosts): - def should_stop_retrying(): + def should_stop_retrying(retry_count=retry_count): time_passed = time.time() - requested_at # if it's the last try or cumulative timeout is done, we stop retrying return retry_count == len(hosts) - 1 or time_passed > http_max_retry_duration diff --git a/ably/realtime/connectionmanager.py b/ably/realtime/connectionmanager.py index 2fea5e2a..ef74caaa 100644 --- a/ably/realtime/connectionmanager.py +++ b/ably/realtime/connectionmanager.py @@ -125,7 +125,7 @@ async def ping(self) -> float: try: response = await self.__ping_future except asyncio.CancelledError: - raise AblyException("Ping request cancelled due to request timeout", 504, 50003) + raise AblyException("Ping request cancelled due to request timeout", 504, 50003) from None return response self.__ping_future = asyncio.Future() @@ -139,7 +139,7 @@ async def ping(self) -> float: try: await asyncio.wait_for(self.__ping_future, self.__timeout_in_secs) except asyncio.TimeoutError: - raise AblyException("Timeout waiting for ping response", 504, 50003) + raise AblyException("Timeout waiting for ping response", 504, 50003) from None ping_end_time = datetime.now().timestamp() response_time_ms = (ping_end_time - ping_start_time) * 1000 diff --git a/ably/rest/auth.py b/ably/rest/auth.py index 2ae771b1..2aaa4b12 100644 --- a/ably/rest/auth.py +++ b/ably/rest/auth.py @@ -186,7 +186,7 @@ async def request_token(self, token_params: dict | None = None, try: token_request = await auth_callback(token_params) except Exception as e: - raise AblyException("auth_callback raised an exception", 401, 40170, cause=e) + raise AblyException("auth_callback raised an exception", 401, 40170, cause=e) from e elif auth_url: log.debug("using token auth with authUrl") @@ -210,7 +210,7 @@ async def request_token(self, token_params: dict | None = None, except TypeError as e: msg = "Expected token request callback to call back with a token string, token request object, or \ token details object" - raise AblyAuthException(msg, 401, 40170, cause=e) + raise AblyAuthException(msg, 401, 40170, cause=e) from e elif isinstance(token_request, str): if len(token_request) == 0: raise AblyAuthException("Token string is empty", 401, 4017) diff --git a/ably/rest/rest.py b/ably/rest/rest.py index 3b034195..a77fcd90 100644 --- a/ably/rest/rest.py +++ b/ably/rest/rest.py @@ -61,9 +61,7 @@ def __init__(self, key: Optional[str] = None, token: Optional[str] = None, else: options = Options(**kwargs) - try: - self._is_realtime - except AttributeError: + if not hasattr(self, '_is_realtime'): self._is_realtime = False self.__http = Http(self, options) diff --git a/ably/transport/websockettransport.py b/ably/transport/websockettransport.py index 0fb7162c..140b9d25 100644 --- a/ably/transport/websockettransport.py +++ b/ably/transport/websockettransport.py @@ -96,7 +96,7 @@ async def ws_connect(self, ws_url, headers): exception = AblyException(f'Error opening websocket connection: {e}', 400, 40000) log.exception(f'WebSocketTransport.ws_connect(): Error opening websocket connection: {exception}') self._emit('failed', exception) - raise exception + raise exception from e async def _handle_websocket_connection(self, ws_url, websocket): log.info(f'ws_connect(): connection established to {ws_url}') diff --git a/ably/types/authoptions.py b/ably/types/authoptions.py index bb15af49..7ee06af7 100644 --- a/ably/types/authoptions.py +++ b/ably/types/authoptions.py @@ -36,7 +36,7 @@ def set_key(self, key): except ValueError: raise AblyException("key of not len 2 parameters: {}" .format(key.split(':')), - 401, 40101) + 401, 40101) from None def replace(self, auth_options): if type(auth_options) is dict: diff --git a/ably/types/mixins.py b/ably/types/mixins.py index 29b43f3a..2d2b6041 100644 --- a/ably/types/mixins.py +++ b/ably/types/mixins.py @@ -101,7 +101,7 @@ def decode(data, encoding='', cipher=None, context=None): except Exception as e: log.error(f'VCDiff decode failed: {e}') - raise AblyException('VCDiff decode failure', 40018, 40018) + raise AblyException('VCDiff decode failure', 40018, 40018) from e elif encoding.startswith(f'{CipherData.ENCODING_ID}+'): if not cipher: diff --git a/ably/util/exceptions.py b/ably/util/exceptions.py index 6523fdaf..31ffa1c7 100644 --- a/ably/util/exceptions.py +++ b/ably/util/exceptions.py @@ -43,7 +43,7 @@ def raise_for_response(response): response.text) raise AblyException(message=response.text, status_code=response.status_code, - code=response.status_code * 100) + code=response.status_code * 100) from None if decoded_response and 'error' in decoded_response: error = decoded_response['error'] @@ -56,7 +56,7 @@ def raise_for_response(response): except KeyError: msg = "Unexpected exception decoding server response: %s" msg = msg % response.text - raise AblyException(message=msg, status_code=500, code=50000) + raise AblyException(message=msg, status_code=500, code=50000) from None raise AblyException(message="", status_code=response.status_code, @@ -91,7 +91,7 @@ async def wrapper(*args, **kwargs): return await func(*args, **kwargs) except Exception as e: log.exception(e) - raise AblyException.from_exception(e) + raise AblyException.from_exception(e) from e return wrapper diff --git a/pyproject.toml b/pyproject.toml index d236755c..e33db01f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,8 +87,8 @@ extend-exclude = [ ] [tool.ruff.lint] -# Enable Pyflakes (F), pycodestyle (E, W), pep8-naming (N), isort (I), and pyupgrade (UP) -select = ["E", "W", "F", "N", "I", "UP"] +# Enable Pyflakes (F), pycodestyle (E, W), pep8-naming (N), isort (I), pyupgrade (UP) and bugbear (B) +select = ["E", "W", "F", "N", "I", "UP", "B"] ignore = [ "N818", # exception name should end in 'Error' "UP026", # mock -> unittest.mock (need mock package for Python 3.7 AsyncMock support) diff --git a/test/ably/rest/restchannelpublish_test.py b/test/ably/rest/restchannelpublish_test.py index 6359649e..f2fcb5ee 100644 --- a/test/ably/rest/restchannelpublish_test.py +++ b/test/ably/rest/restchannelpublish_test.py @@ -402,7 +402,7 @@ async def test_interoperability(self): response = await channel.publish(data=expected_value) assert response.status_code == 201 - async def check_data(): + async def check_data(encoding=encoding, msg_data=msg_data): async with httpx.AsyncClient(http2=True) as client: r = await client.get(url, auth=auth) item = r.json()[0] @@ -418,7 +418,7 @@ async def check_data(): response = await channel.publish(messages=[Message(data=msg_data, encoding=encoding)]) assert response.status_code == 201 - async def check_history(): + async def check_history(expected_value=expected_value, expected_type=expected_type): history = await channel.history() message = history.items[0] return message.data == expected_value and isinstance(message.data, type_mapping[expected_type]) diff --git a/test/ably/rest/restpush_test.py b/test/ably/rest/restpush_test.py index 813efb4d..dba3d6a4 100644 --- a/test/ably/rest/restpush_test.py +++ b/test/ably/rest/restpush_test.py @@ -26,7 +26,7 @@ async def asyncSetUp(self): # Register several devices for later use self.devices = {} - for i in range(10): + for _ in range(10): await self.save_device() # Register several subscriptions for later use @@ -253,7 +253,7 @@ async def test_admin_device_registrations_remove_where(self): assert remove_boo_device_response.status_code == 204 # Doesn't exist (Deletion is async: wait up to a few seconds before giving up) with pytest.raises(AblyException): - for i in range(5): + for _ in range(5): time.sleep(1) await get(device.id) diff --git a/test/ably/utils.py b/test/ably/utils.py index 4ce16886..ae89c632 100644 --- a/test/ably/utils.py +++ b/test/ably/utils.py @@ -196,7 +196,7 @@ async def assert_waiter(block: Callable[[], Awaitable[bool]], timeout: float = 1 try: await asyncio.wait_for(_poll_until_success(block), timeout=timeout) except asyncio.TimeoutError: - raise asyncio.TimeoutError(f"Condition not met within {timeout}s") + raise asyncio.TimeoutError(f"Condition not met within {timeout}s") from None async def _poll_until_success(block: Callable[[], Awaitable[bool]]) -> None: From c1d2752b3c04d2105df298ef011315dc19aeab1e Mon Sep 17 00:00:00 2001 From: owenpearson Date: Tue, 2 Dec 2025 16:35:42 +0000 Subject: [PATCH 4/5] ci: add comprehensions linting and fix all violations --- pyproject.toml | 4 ++-- test/ably/rest/restchannelpublish_test.py | 4 ++-- test/ably/rest/restcrypto_test.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e33db01f..bd0964cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,8 +87,8 @@ extend-exclude = [ ] [tool.ruff.lint] -# Enable Pyflakes (F), pycodestyle (E, W), pep8-naming (N), isort (I), pyupgrade (UP) and bugbear (B) -select = ["E", "W", "F", "N", "I", "UP", "B"] +# Enable Pyflakes (F), pycodestyle (E, W), pep8-naming (N), isort (I), pyupgrade (UP), bugbear (B) and comprehensions (C4) +select = ["E", "W", "F", "N", "I", "UP", "B", "C4"] ignore = [ "N818", # exception name should end in 'Error' "UP026", # mock -> unittest.mock (need mock package for Python 3.7 AsyncMock support) diff --git a/test/ably/rest/restchannelpublish_test.py b/test/ably/rest/restchannelpublish_test.py index f2fcb5ee..56d1eeb0 100644 --- a/test/ably/rest/restchannelpublish_test.py +++ b/test/ably/rest/restchannelpublish_test.py @@ -56,7 +56,7 @@ async def test_publish_various_datatypes_text(self): assert messages is not None, "Expected non-None messages" assert len(messages) == 4, "Expected 4 messages" - message_contents = dict((m.name, m.data) for m in messages) + message_contents = {m.name: m.data for m in messages} log.debug(f"message_contents: {str(message_contents)}") assert message_contents["publish0"] == "This is a string message payload", \ @@ -494,7 +494,7 @@ async def test_message_serialization(self): } message = Message(**data) request_body = channel._Channel__publish_request_body(messages=[message]) - input_keys = set(case.snake_to_camel(x) for x in data.keys()) + input_keys = {case.snake_to_camel(x) for x in data.keys()} assert input_keys - set(request_body) == set() # RSL1k1 diff --git a/test/ably/rest/restcrypto_test.py b/test/ably/rest/restcrypto_test.py index 6b31f0c3..1ee02995 100644 --- a/test/ably/rest/restcrypto_test.py +++ b/test/ably/rest/restcrypto_test.py @@ -74,7 +74,7 @@ async def test_crypto_publish(self): assert messages is not None, "Expected non-None messages" assert 4 == len(messages), "Expected 4 messages" - message_contents = dict((m.name, m.data) for m in messages) + message_contents = {m.name: m.data for m in messages} log.debug(f"message_contents: {str(message_contents)}") assert "This is a string message payload" == message_contents["publish3"],\ @@ -107,7 +107,7 @@ async def test_crypto_publish_256(self): assert messages is not None, "Expected non-None messages" assert 4 == len(messages), "Expected 4 messages" - message_contents = dict((m.name, m.data) for m in messages) + message_contents = {m.name: m.data for m in messages} log.debug(f"message_contents: {str(message_contents)}") assert "This is a string message payload" == message_contents["publish3"],\ @@ -156,7 +156,7 @@ async def test_crypto_send_unencrypted(self): assert messages is not None, "Expected non-None messages" assert 4 == len(messages), "Expected 4 messages" - message_contents = dict((m.name, m.data) for m in messages) + message_contents = {m.name: m.data for m in messages} log.debug(f"message_contents: {str(message_contents)}") assert "This is a string message payload" == message_contents["publish3"],\ From 33b9d3ca471c925df4bac6f18c717fd50324af90 Mon Sep 17 00:00:00 2001 From: owenpearson Date: Tue, 2 Dec 2025 17:22:58 +0000 Subject: [PATCH 5/5] fix: use python 3.7 compatible `AsyncMock` --- test/ably/rest/encoders_test.py | 2 +- test/ably/rest/restauth_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ably/rest/encoders_test.py b/test/ably/rest/encoders_test.py index 001eefbe..9c30ded9 100644 --- a/test/ably/rest/encoders_test.py +++ b/test/ably/rest/encoders_test.py @@ -15,7 +15,7 @@ if sys.version_info >= (3, 8): from unittest.mock import AsyncMock else: - from unittest.mock import AsyncMock + from mock import AsyncMock log = logging.getLogger(__name__) diff --git a/test/ably/rest/restauth_test.py b/test/ably/rest/restauth_test.py index dc5d4fe6..854691e3 100644 --- a/test/ably/rest/restauth_test.py +++ b/test/ably/rest/restauth_test.py @@ -19,7 +19,7 @@ if sys.version_info >= (3, 8): from unittest.mock import AsyncMock else: - from unittest.mock import AsyncMock + from mock import AsyncMock log = logging.getLogger(__name__)