Skip to content

Commit 4fb2d61

Browse files
committed
Merge branch 'main' into endpoint/get_auth_user
2 parents 4503c2d + c4b8257 commit 4fb2d61

File tree

9 files changed

+72
-23
lines changed

9 files changed

+72
-23
lines changed

.github/dependabot.yml

Lines changed: 0 additions & 6 deletions
This file was deleted.

.github/workflows/coverage_lint_build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ jobs:
125125
id-token: write
126126

127127
steps:
128-
- uses: actions/download-artifact@v4
128+
- uses: actions/download-artifact@v6
129129

130130
- name: Copy artifacts to dist/ folder
131131
run: |

twitchio/authentication/scopes.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,3 +413,33 @@ def selected(self) -> list[str]:
413413
def all(cls) -> Scopes:
414414
"""Classmethod which creates this :class:`.Scopes` object with all scopes selected."""
415415
return cls([scope for scope in cls.__dict__.values() if isinstance(scope, _scope_property)])
416+
417+
@classmethod
418+
def from_url(cls, url: str) -> Scopes:
419+
"""Classmethod which attempts to create this :class:`.Scopes` object from a URL containing scopes as a query parameter."""
420+
self = cls()
421+
422+
if not url.startswith("http"):
423+
url = url.replace("?scopes=", "")
424+
url = f"http://localhost:4343?scopes={url}"
425+
426+
parsed = urllib.parse.parse_qs(url)
427+
scopes: list[str] = []
428+
429+
for qs in parsed.values():
430+
for s in qs:
431+
unquoted = urllib.parse.unquote_plus(s)
432+
splat = unquoted.split()
433+
scopes.extend(splat)
434+
435+
for scope in scopes:
436+
if scope == "openid":
437+
continue
438+
439+
prop = getattr(self, scope.replace(":", "_"), None)
440+
if not prop:
441+
continue
442+
443+
self._selected.add(prop)
444+
445+
return self

twitchio/client.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,17 @@ def __init__(
149149
msg = "If you require the StarletteAdapter please install the required packages: 'pip install twitchio[starlette]'."
150150
logger.warning(msg)
151151

152-
adapter: BaseAdapter | type[BaseAdapter] = options.get("adapter", AiohttpAdapter)
152+
self._adapter: BaseAdapter[Any] | AiohttpAdapter[Self]
153+
adapter: BaseAdapter[Any] | type[BaseAdapter[Any]] | None = options.get("adapter")
154+
153155
if isinstance(adapter, BaseAdapter):
154-
adapter.client = self
155156
self._adapter = adapter
157+
elif adapter is None:
158+
self._adapter = AiohttpAdapter()
156159
else:
157160
self._adapter = adapter()
161+
162+
if not hasattr(self._adapter, "client"):
158163
self._adapter.client = self
159164

160165
# Own Client User. Set in login...
@@ -178,12 +183,12 @@ def __init__(
178183
self._setup_called = False
179184

180185
@property
181-
def adapter(self) -> BaseAdapter:
186+
def adapter(self) -> BaseAdapter[Any]:
182187
"""Property returning the :class:`~twitchio.AiohttpAdapter` or :class:`~twitchio.StarlettepAdapter` the bot is
183188
currently running."""
184189
return self._adapter
185190

186-
async def set_adapter(self, adapter: BaseAdapter) -> None:
191+
async def set_adapter(self, adapter: BaseAdapter[Any]) -> None:
187192
"""|coro|
188193
189194
Method which sets and starts a new web adapter which inherits from either :class:`~twitchio.AiohttpAdapter` or
@@ -202,7 +207,8 @@ async def set_adapter(self, adapter: BaseAdapter) -> None:
202207
await self._adapter.close(False)
203208

204209
self._adapter = adapter
205-
self._adapter.client = self
210+
if not hasattr(self._adapter, "client"):
211+
self._adapter.client = self
206212

207213
if self._setup_called and not self._adapter._running:
208214
await self._adapter.run()

twitchio/eventsub/websockets.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ async def _reconnect(self, url: str) -> None:
299299
client=self._client,
300300
token_for=self._token_for,
301301
http=self._http,
302+
shard_id=self._shard_id,
302303
)
303304

304305
socket._subscriptions = self._subscriptions

twitchio/types_/options.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class ClientOptions(TypedDict, total=False):
4343
redirect_uri: str | None
4444
scopes: Scopes | None
4545
session: aiohttp.ClientSession | None
46-
adapter: NotRequired[BaseAdapter]
46+
adapter: NotRequired[BaseAdapter[Any]]
4747
fetch_client_user: NotRequired[bool]
4848

4949

twitchio/web/aio_adapter.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import datetime
2929
import logging
3030
from collections import deque
31-
from typing import TYPE_CHECKING, Any, cast
31+
from typing import TYPE_CHECKING, Any, TypeVar, cast
3232
from urllib.parse import unquote_plus
3333

3434
from aiohttp import web
@@ -54,10 +54,11 @@
5454
__all__ = ("AiohttpAdapter",)
5555

5656

57+
BT = TypeVar("BT", bound="Client")
5758
logger: logging.Logger = logging.getLogger(__name__)
5859

5960

60-
class AiohttpAdapter(BaseAdapter, web.Application):
61+
class AiohttpAdapter(BaseAdapter[BT], web.Application):
6162
"""The AiohttpAdapter for OAuth and Webhook based EventSub.
6263
6364
This adapter uses ``aiohttp`` which is a base dependency and should be installed and available with Twitchio.
@@ -113,6 +114,9 @@ class AiohttpAdapter(BaseAdapter, web.Application):
113114
E.g. ``http://localhost:4343/oauth/callback`` or ``https://mydomain.org/oauth/callback``.
114115
ssl_context: SSLContext | None
115116
An optional :class:`SSLContext` passed to the adapter. If SSL is setup via a front-facing web server such as NGINX, you should leave this as None.
117+
client: :class:`~twitchio.Client` | None
118+
An optional :class:`~twitchio.Client` or any derivative such as :class:`~twitchio.ext.commands.Bot` to set for this
119+
adapter. When ``None`` the client will be set automatically after initalization. Defaults to ``None``.
116120
117121
Examples
118122
--------
@@ -141,7 +145,7 @@ def __init__(self) -> None:
141145
super().__init__(adapter=adapter)
142146
"""
143147

144-
client: Client
148+
client: BT
145149

146150
def __init__(
147151
self,
@@ -154,8 +158,13 @@ def __init__(
154158
oauth_path: str | None = None,
155159
redirect_path: str | None = None,
156160
ssl_context: SSLContext | None = None,
161+
client: BT | None = None,
157162
) -> None:
158163
super().__init__()
164+
165+
if client:
166+
self.client = client
167+
159168
self._runner: web.AppRunner | None = None
160169

161170
self._host: str = host or "localhost"
@@ -194,7 +203,7 @@ def __init__(
194203
self._responded: deque[str] = deque(maxlen=5000)
195204
self._running: bool = False
196205

197-
def __init_subclass__(cls: type[AiohttpAdapter]) -> None:
206+
def __init_subclass__(cls: type[AiohttpAdapter[BT]]) -> None:
198207
return
199208

200209
def __repr__(self) -> str:

twitchio/web/starlette_adapter.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import datetime
2929
import logging
3030
from collections import deque
31-
from typing import TYPE_CHECKING, Any, cast
31+
from typing import TYPE_CHECKING, Any, TypeVar, cast
3232
from urllib.parse import unquote_plus
3333

3434
import uvicorn
@@ -57,10 +57,11 @@
5757
__all__ = ("StarletteAdapter",)
5858

5959

60+
BT = TypeVar("BT", bound="Client")
6061
logger: logging.Logger = logging.getLogger(__name__)
6162

6263

63-
class StarletteAdapter(BaseAdapter, Starlette):
64+
class StarletteAdapter(BaseAdapter[BT], Starlette):
6465
"""The StarletteAdapter for OAuth and Webhook based EventSub.
6566
6667
This adapter uses ``starlette`` which is an optional dependency and needs to be installed.
@@ -125,6 +126,9 @@ class StarletteAdapter(BaseAdapter, Starlette):
125126
timeout_graceful_shutdown: int
126127
An optional :class:`int` which is the maximum amount of time in seconds ``Uvicorn`` should wait before forcefully
127128
closing. Defaults to ``3``.
129+
client: :class:`~twitchio.Client` | None
130+
An optional :class:`~twitchio.Client` or any derivative such as :class:`~twitchio.ext.commands.Bot` to set for this
131+
adapter. When ``None`` the client will be set automatically after initalization. Defaults to ``None``.
128132
129133
Examples
130134
--------
@@ -153,7 +157,7 @@ def __init__(self) -> None:
153157
super().__init__(adapter=adapter)
154158
"""
155159

156-
client: Client
160+
client: BT
157161

158162
def __init__(
159163
self,
@@ -170,7 +174,11 @@ def __init__(
170174
ssl_certfile: str | PathLike[str] | None = None,
171175
timeout_keep_alive: int = 5,
172176
timeout_graceful_shutdown: int = 3,
177+
client: BT | None = None,
173178
) -> None:
179+
if client:
180+
self.client = client
181+
174182
self._timeout_keep_alive = timeout_keep_alive
175183
self._timeout_graceful_shutdown = timeout_graceful_shutdown
176184
self._host: str = host or "localhost"

twitchio/web/utils.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import hashlib
2929
import hmac
3030
import logging
31-
from typing import TYPE_CHECKING, Any
31+
from typing import TYPE_CHECKING, Any, Generic, TypeVar
3232

3333
from aiohttp import web
3434

@@ -45,6 +45,7 @@
4545
from ..types_.eventsub import EventSubHeaders
4646

4747

48+
BT = TypeVar("BT", bound="Client")
4849
logger: logging.Logger = logging.getLogger(__name__)
4950

5051

@@ -82,8 +83,8 @@ def __init__(
8283
self.exception = exception
8384

8485

85-
class BaseAdapter(abc.ABC):
86-
client: Client
86+
class BaseAdapter(abc.ABC, Generic[BT]):
87+
client: BT
8788
_runner_task: asyncio.Task[None] | None
8889
_eventsub_secret: str | None
8990
_running: bool

0 commit comments

Comments
 (0)