Skip to content

Commit ea23c02

Browse files
committed
Bump Python min version to 3.11
1 parent 690bf19 commit ea23c02

File tree

11 files changed

+41
-108
lines changed

11 files changed

+41
-108
lines changed

pyproject.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@ authors = [{ name = "PythonistaGuild" }]
88
dynamic = ["dependencies", "version"]
99
description = "A fully asynchronous and modern Python wrapper around the Twitch API."
1010
readme = "README.md"
11-
requires-python = ">=3.10"
11+
requires-python = ">=3.11"
1212
classifiers = [
1313
"License :: OSI Approved :: MIT License",
1414
"Intended Audience :: Developers",
1515
"Natural Language :: English",
1616
"Operating System :: OS Independent",
17-
"Programming Language :: Python :: 3.10",
1817
"Programming Language :: Python :: 3.11",
1918
"Programming Language :: Python :: 3.12",
2019
"Topic :: Internet",
@@ -104,4 +103,4 @@ useLibraryCodeForTypes = true
104103
typeCheckingMode = "strict"
105104
reportImportCycles = false
106105
reportPrivateUsage = false
107-
pythonVersion = "3.10"
106+
pythonVersion = "3.11"

requirements.txt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1 @@
1-
aiohttp>=3.9.1,<4
2-
typing_extensions>=4.1.0
3-
backports.datetime_fromisoformat>=2.0.1 ; python_version < "3.11"
4-
async-timeout ; python_version < "3.11"
1+
aiohttp>=3.9.1,<4

twitchio/client.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import asyncio
2828
import logging
2929
from collections import defaultdict
30-
from typing import TYPE_CHECKING, Any, Literal
30+
from typing import TYPE_CHECKING, Any, Literal, Self, Unpack
3131

3232
from .authentication import ManagedHTTPClient, Scopes
3333
from .conduits import Conduit, ConduitPool
@@ -48,7 +48,6 @@
4848
from collections.abc import Awaitable, Callable, Coroutine
4949

5050
import aiohttp
51-
from typing_extensions import Self, Unpack
5251

5352
from .authentication import ClientCredentialsPayload, ValidateTokenPayload
5453
from .http import HTTPAsyncIterator

twitchio/conduits/pool.py

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,22 @@
2626

2727
import logging
2828
from types import MappingProxyType
29-
from typing import TYPE_CHECKING
29+
from typing import TYPE_CHECKING, Self
3030

3131
from twitchio.eventsub.enums import ShardStatus, TransportMethod
3232
from twitchio.types_.conduits import ShardData
3333

3434
from ..utils import parse_timestamp
35-
from .websockets import Websocket
3635

3736

3837
if TYPE_CHECKING:
3938
import datetime
4039
from collections.abc import Mapping
4140

42-
from typing_extensions import Self
43-
4441
from ..client import Client
4542
from ..ext.commands import Bot
4643
from ..types_.conduits import ConduitData, ShardData, ShardTransport
44+
from .websockets import Websocket
4745

4846

4947
logger: logging.Logger = logging.getLogger(__name__)
@@ -136,40 +134,3 @@ def __init__(self, *, client: Client | Bot) -> None:
136134
@property
137135
def conduits(self) -> Mapping[str, Conduit]:
138136
return MappingProxyType(self._conduits) # thanks lilly
139-
140-
async def create_conduit(self, shard_count: int, buffer: bool = False) -> list[Conduit]:
141-
buffer_: int = min(50, max(int(shard_count * 0.1), 1)) if buffer else shard_count
142-
143-
real_count: int = min(shard_count + buffer_, 20_000)
144-
if shard_count > 20_000:
145-
logger.warning('"shard_count" parameter for "create_conduit" exceeds 20,000. Reducing count to 20,000.')
146-
147-
# TODO: Handle 429 for 5 Conduits...
148-
conduits: list[Conduit] = await self._client._create_conduit(real_count)
149-
for conduit in conduits:
150-
self._conduits[conduit.id] = conduit
151-
152-
return conduits
153-
154-
async def fetch_conduits(self) -> Mapping[str, Conduit]:
155-
data = await self._client._http.get_conduits()
156-
mapping: dict[str, Conduit] = {}
157-
158-
for payload in data["data"]:
159-
conduit = Conduit(data=payload, pool=self)
160-
mapping[conduit.id] = conduit
161-
162-
self._conduits = mapping
163-
return MappingProxyType(mapping)
164-
165-
async def test(self) -> None:
166-
await self.fetch_conduits()
167-
168-
for id_, conduit in self._conduits.items():
169-
shards: list[Shard] = await (await self._client._http.get_conduit_shards(id_))
170-
start: int = len(shards)
171-
172-
for n in range(start, conduit._shard_count):
173-
websocket: Websocket = Websocket(id=n)
174-
await websocket.connect()
175-
break

twitchio/conduits/websockets.py

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
WelcomeMessage,
4141
WelcomePayload,
4242
)
43-
from ..utils import _from_json, a_timeout # type: ignore
43+
from ..utils import _from_json # type: ignore
4444

4545

4646
logger: logging.Logger = logging.getLogger(__name__)
@@ -82,23 +82,24 @@ def connected(self) -> bool:
8282
def id(self) -> str:
8383
return self._id
8484

85-
async def connect(self) -> None:
86-
url: str = f"{WSS}?keepalive_timeout_seconds={self._keep_alive_timeout}"
85+
async def connect(self, *, reconnect_url: str | None = None) -> None:
86+
url: str = reconnect_url or f"{WSS}?keepalive_timeout_seconds={self._keep_alive_timeout}"
8787

8888
if self.connected:
89-
logger.warning("Trying to connect to an already running conduit websocket with ID: %s.", self._session_id)
89+
logger.warning("Trying to connect to an already running eventsub websocket with ID: %s.", self._session_id)
9090
return
9191

9292
if not self._session:
9393
self._session = aiohttp.ClientSession()
9494

9595
self._ready.clear()
96-
9796
self._socket = await self._session.ws_connect(url, heartbeat=15.0)
98-
self._listen_task = asyncio.create_task(self._listen())
97+
98+
if not self._listen_task:
99+
self._listen_task = asyncio.create_task(self._listen())
99100

100101
try:
101-
async with a_timeout(10):
102+
async with asyncio.timeout(10):
102103
await self._ready.wait()
103104
except TimeoutError:
104105
raise WebsocketTimeoutException
@@ -124,22 +125,22 @@ async def _listen(self) -> None:
124125
type_: aiohttp.WSMsgType = message.type
125126
if type_ in (aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.CLOSE, aiohttp.WSMsgType.CLOSING):
126127
logger.debug(
127-
"Received close message [%s] on conduit websocket: %s",
128+
"Received close message [%s] on eventsub websocket: %s",
128129
self._socket.close_code,
129130
self._session_id,
130131
)
131132
return await self.close()
132133

133134
if type_ is not aiohttp.WSMsgType.TEXT:
134-
logger.debug("Received unknown message from conduit websocket: %s", self._session_id)
135+
logger.debug("Received unknown message from eventsub websocket: %s", self._session_id)
135136
continue
136137

137138
self._last_keepalive = datetime.datetime.now()
138139

139140
try:
140141
data: WebsocketMessages = cast(WebsocketMessages, _from_json(message.data))
141142
except Exception:
142-
logger.warning("Unable to parse JSON in conduit websocket: %s", self._session_id)
143+
logger.warning("Unable to parse JSON in eventsub websocket: %s", self._session_id)
143144
continue
144145

145146
metadata: MetaData = data["metadata"]
@@ -150,31 +151,31 @@ async def _listen(self) -> None:
150151
await self._process_welcome(welcome_data)
151152

152153
elif message_type == "session_reconnect":
153-
logger.debug('Received "session_reconnect" message from conduit websocket: %s', self._session_id)
154+
logger.debug('Received "session_reconnect" message from eventsub websocket: %s', self._session_id)
154155
reconnect_data: ReconnectMessage = cast(ReconnectMessage, data)
155156
await self._process_reconnect(reconnect_data)
156157

157158
elif message_type == "session_keepalive":
158-
logger.debug('Received "session_keepalive" message from conduit websocket: %s', self._session_id)
159+
logger.debug('Received "session_keepalive" message from eventsub websocket: %s', self._session_id)
159160

160161
elif message_type == "revocation":
161-
logger.debug('Received "revocation" message from conduit websocket: %s', self._session_id)
162+
logger.debug('Received "revocation" message from eventsub websocket: %s', self._session_id)
162163

163164
revocation_data: RevocationMessage = cast(RevocationMessage, data)
164165
await self._process_revocation(revocation_data)
165166

166167
elif message_type == "notification":
167-
logger.debug('Received "notification" message from conduit websocket: %s', self._session_id)
168+
logger.debug('Received "notification" message from eventsub websocket: %s', self._session_id)
168169

169170
notification_data: NotificationMessage = cast(NotificationMessage, data)
170171
await self._process_notification(notification_data)
171172

172173
else:
173-
logger.warning("Received an unknown message type in conduit websocket: %s", self._session_id)
174+
logger.warning("Received an unknown message type in eventsub websocket: %s", self._session_id)
174175

175176
async def _process_keepalive(self) -> None:
176177
assert self._last_keepalive
177-
logger.debug("Started keep_alive task on conduit websocket: %s", self._session_id)
178+
logger.debug("Started keep_alive task on eventsub websocket: %s", self._session_id)
178179

179180
while True:
180181
await asyncio.sleep(self._keep_alive_timeout)
@@ -188,9 +189,11 @@ async def _process_welcome(self, data: WelcomeMessage) -> None:
188189
self._session_id = payload["session"]["id"]
189190
self._ready.set()
190191

191-
logger.debug('Received "session_welcome" message from conduit websocket: %s', self._session_id)
192+
logger.debug('Received "session_welcome" message from eventsub websocket: %s', self._session_id)
192193

193-
async def _process_reconnect(self, data: ReconnectMessage) -> None: ...
194+
async def _process_reconnect(self, data: ReconnectMessage) -> None:
195+
logger.info("Attempting to reconnect eventsub websocket: '%s'", self._id)
196+
await self.connect(reconnect_url=data["payload"]["session"]["reconnect_url"])
194197

195198
async def _process_revocation(self, data: RevocationMessage) -> None: ...
196199

@@ -201,18 +204,18 @@ async def close(self) -> None:
201204
try:
202205
await self._socket.close()
203206
except Exception:
204-
...
207+
pass
205208

206209
if self._session:
207210
try:
208211
await self._session.close()
209212
except Exception:
210-
...
213+
pass
211214

212215
if self._listen_task:
213216
try:
214217
self._listen_task.cancel()
215218
except Exception:
216-
...
219+
pass
217220

218-
logger.debug("Successfully closed conduit websocket: %s", self._session_id)
221+
logger.debug("Successfully closed eventsub websocket: %s", self._session_id)

twitchio/eventsub/payloads.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,10 @@
2424

2525
from __future__ import annotations
2626

27-
from typing import TYPE_CHECKING, Any, ClassVar, Literal
27+
from typing import TYPE_CHECKING, Any, ClassVar, Literal, Unpack
2828

2929

3030
if TYPE_CHECKING:
31-
from typing_extensions import Unpack
32-
3331
from ..types_.conduits import Condition
3432

3533

twitchio/http.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
import urllib.parse
3232
from collections import deque
3333
from collections.abc import AsyncIterator, Awaitable, Callable
34-
from typing import TYPE_CHECKING, Any, ClassVar, Generic, Literal, TypeAlias, TypeVar
34+
from typing import TYPE_CHECKING, Any, ClassVar, Generic, Literal, Self, TypeAlias, TypeVar, Unpack
3535

3636
import aiohttp
3737

@@ -63,8 +63,6 @@
6363
if TYPE_CHECKING:
6464
from collections.abc import Generator, Sequence
6565

66-
from typing_extensions import Self, Unpack
67-
6866
from .assets import Asset
6967
from .models.channel_points import CustomReward
7068
from .models.moderation import AutomodCheckMessage, AutomodSettings
@@ -1938,7 +1936,7 @@ async def post_channel_stream_schedule_segment(
19381936
_start_time = (
19391937
start_time.isoformat()
19401938
if start_time.tzinfo is not None
1941-
else start_time.replace(tzinfo=datetime.timezone.utc).isoformat()
1939+
else start_time.replace(tzinfo=datetime.UTC).isoformat()
19421940
)
19431941

19441942
data = {
@@ -1976,7 +1974,7 @@ async def patch_channel_stream_schedule_segment(
19761974
_start_time = (
19771975
start_time.isoformat()
19781976
if start_time.tzinfo is not None
1979-
else start_time.replace(tzinfo=datetime.timezone.utc).isoformat()
1977+
else start_time.replace(tzinfo=datetime.UTC).isoformat()
19801978
)
19811979
data["start_time"] = _start_time
19821980
if category_id is not None:

twitchio/types_/conduits.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@
2222
SOFTWARE.
2323
"""
2424

25-
from typing import Any, Literal, TypeAlias, TypedDict
26-
27-
from typing_extensions import Never, NotRequired
25+
from typing import Any, Literal, Never, NotRequired, TypeAlias, TypedDict
2826

2927

3028
class ShardTransport(TypedDict):

twitchio/types_/responses.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424

2525
from typing import Any, Generic, Literal, TypeAlias, TypedDict, TypeVar
2626

27-
from typing_extensions import TypedDict as TypedDictExt
28-
2927
from .conduits import ConduitData, ShardData
3028

3129

@@ -87,7 +85,7 @@
8785
T = TypeVar("T")
8886

8987

90-
class Payload(TypedDictExt, Generic[T]):
88+
class Payload(TypedDict, Generic[T]):
9189
data: list[T]
9290

9391

twitchio/user.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
)
6969
from .models.polls import Poll
7070
from .models.predictions import Prediction
71-
from .models.schedule import Schedule
71+
from .models.schedule import Schedule # noqa: TCH004
7272
from .models.streams import Stream, StreamMarker, VideoMarkers
7373
from .models.subscriptions import BroadcasterSubscriptions, UserSubscription
7474
from .models.teams import ChannelTeam

twitchio/utils.py

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,17 @@
77
import pathlib
88
import struct
99
import sys
10-
from datetime import datetime, timezone
11-
from typing import TYPE_CHECKING, Any
10+
from datetime import UTC, datetime
11+
from typing import TYPE_CHECKING, Any, Self
1212
from urllib.parse import quote
1313

1414

1515
if TYPE_CHECKING:
1616
from collections.abc import Generator
1717

18-
from typing_extensions import Self
19-
2018
from .types_.colours import Colours
2119

2220

23-
try:
24-
from backports.datetime_fromisoformat import MonkeyPatch # type: ignore
25-
26-
MonkeyPatch.patch_fromisoformat() # type: ignore
27-
except ImportError:
28-
pass
29-
3021
try:
3122
import orjson # type: ignore
3223

@@ -35,15 +26,6 @@
3526
_from_json = json.loads
3627

3728

38-
try:
39-
from asyncio.timeouts import _timeout # type: ignore
40-
except ImportError:
41-
from async_timeout import timeout as _timeout # type: ignore
42-
43-
44-
a_timeout = _timeout # type: ignore
45-
46-
4729
__all__ = ("_from_json", "setup_logging", "ColourFormatter", "ColorFormatter", "parse_timestamp", "url_encode_datetime")
4830

4931

@@ -436,6 +418,6 @@ def url_encode_datetime(dt: datetime) -> str:
436418
str
437419
The URL encoded parsed datetime object.
438420
"""
439-
formatted_dt = dt.replace(tzinfo=timezone.utc).isoformat() if dt.tzinfo is None else dt.isoformat()
421+
formatted_dt = dt.replace(tzinfo=UTC).isoformat() if dt.tzinfo is None else dt.isoformat()
440422

441423
return quote(formatted_dt)

0 commit comments

Comments
 (0)