Skip to content

Commit 0f92d17

Browse files
authored
Add support for fetching channels (#51)
1 parent fe84c8e commit 0f92d17

6 files changed

Lines changed: 165 additions & 6 deletions

File tree

src/youtubeaio/youtube.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
build_url,
1313
first,
1414
)
15-
from youtubeaio.models import YouTubeVideo
15+
from youtubeaio.models import YouTubeChannel, YouTubeVideo
1616
from youtubeaio.types import (
1717
AuthScope,
1818
MissingScopeError,
@@ -191,14 +191,47 @@ async def get_videos(
191191
"videos",
192192
param,
193193
YouTubeVideo,
194-
split_lists=True,
195194
):
196195
yield item # type: ignore[misc]
197196

198197
async def get_video(self, video_id: str) -> YouTubeVideo | None:
199198
"""Get a single video."""
200199
return await first(self.get_videos([video_id]))
201200

201+
async def _get_channels(
202+
self,
203+
param: dict[str, Any],
204+
) -> AsyncGenerator[YouTubeChannel, None]:
205+
"""Get channels."""
206+
async for item in self._build_generator(
207+
"GET",
208+
"channels",
209+
param,
210+
YouTubeChannel,
211+
):
212+
yield item # type: ignore[misc]
213+
214+
async def get_user_channels(self) -> AsyncGenerator[YouTubeChannel, None]:
215+
"""Return channels owned by the authenticated user."""
216+
param = {
217+
"part": "snippet",
218+
"mine": "true",
219+
}
220+
async for item in self._get_channels(param):
221+
yield item
222+
223+
async def get_channels(
224+
self,
225+
channel_ids: list[str],
226+
) -> AsyncGenerator[YouTubeChannel, None]:
227+
"""Return list of channels."""
228+
param = {
229+
"part": "snippet",
230+
"id": channel_ids,
231+
}
232+
async for item in self._get_channels(param):
233+
yield item
234+
202235
async def close(self) -> None:
203236
"""Close open client session."""
204237
if self.session and self._close_session:

tests/const.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""Constants for YouTube tests."""
2+
3+
YOUTUBE_URL = "youtube.googleapis.com"
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"kind": "youtube#channelListResponse",
3+
"etag": "sM5ffk5W79swi2V870w6CmMlPgs",
4+
"pageInfo": {
5+
"totalResults": 1,
6+
"resultsPerPage": 5
7+
},
8+
"items": [
9+
{
10+
"kind": "youtube#channel",
11+
"etag": "ieb4dig-VKDKW3FEut81IwUEiog",
12+
"id": "UC_x5XG1OV2P6uZZ5FSM9Ttw",
13+
"snippet": {
14+
"title": "Google for Developers",
15+
"description": "Subscribe to join a community of creative developers and learn the latest in Google technology — from AI and cloud, to mobile and web.\n\nExplore more at developers.google.com\n\n",
16+
"customUrl": "@googledevelopers",
17+
"publishedAt": "2007-08-23T00:34:43Z",
18+
"thumbnails": {
19+
"default": {
20+
"url": "https://yt3.ggpht.com/fca_HuJ99xUxflWdex0XViC3NfctBFreIl8y4i9z411asnGTWY-Ql3MeH_ybA4kNaOjY7kyA=s88-c-k-c0x00ffffff-no-rj",
21+
"width": 88,
22+
"height": 88
23+
},
24+
"medium": {
25+
"url": "https://yt3.ggpht.com/fca_HuJ99xUxflWdex0XViC3NfctBFreIl8y4i9z411asnGTWY-Ql3MeH_ybA4kNaOjY7kyA=s240-c-k-c0x00ffffff-no-rj",
26+
"width": 240,
27+
"height": 240
28+
},
29+
"high": {
30+
"url": "https://yt3.ggpht.com/fca_HuJ99xUxflWdex0XViC3NfctBFreIl8y4i9z411asnGTWY-Ql3MeH_ybA4kNaOjY7kyA=s800-c-k-c0x00ffffff-no-rj",
31+
"width": 800,
32+
"height": 800
33+
}
34+
},
35+
"localized": {
36+
"title": "Google for Developers",
37+
"description": "Subscribe to join a community of creative developers and learn the latest in Google technology — from AI and cloud, to mobile and web.\n\nExplore more at developers.google.com\n\n"
38+
},
39+
"country": "US"
40+
}
41+
}
42+
]
43+
}

tests/test_channel.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
"""Tests for the YouTube client."""
2+
from datetime import datetime, timezone
3+
4+
import aiohttp
5+
import pytest
6+
from aresponses import ResponsesMockServer
7+
8+
from youtubeaio.youtube import YouTube
9+
10+
from . import load_fixture
11+
from .const import YOUTUBE_URL
12+
13+
14+
async def test_fetch_channel(
15+
aresponses: ResponsesMockServer,
16+
) -> None:
17+
"""Test retrieving a channel."""
18+
aresponses.add(
19+
YOUTUBE_URL,
20+
"/youtube/v3/channels",
21+
"GET",
22+
aresponses.Response(
23+
status=200,
24+
headers={"Content-Type": "application/json"},
25+
text=load_fixture("channel_response_snippet.json"),
26+
),
27+
)
28+
async with aiohttp.ClientSession() as session:
29+
youtube = YouTube(session=session)
30+
channel_generator = youtube.get_channels(
31+
channel_ids=["UC_x5XG1OV2P6uZZ5FSM9Ttw"],
32+
)
33+
channel = await channel_generator.__anext__()
34+
assert channel
35+
assert channel.snippet
36+
assert channel.snippet.published_at == datetime(
37+
2007,
38+
8,
39+
23,
40+
0,
41+
34,
42+
43,
43+
tzinfo=timezone.utc,
44+
)
45+
with pytest.raises(StopAsyncIteration):
46+
await channel_generator.__anext__()
47+
await youtube.close()
48+
49+
50+
async def test_fetch_own_channel(
51+
aresponses: ResponsesMockServer,
52+
) -> None:
53+
"""Test retrieving own channel."""
54+
aresponses.add(
55+
YOUTUBE_URL,
56+
"/youtube/v3/channels?part=snippet&mine=true",
57+
"GET",
58+
aresponses.Response(
59+
status=200,
60+
headers={"Content-Type": "application/json"},
61+
text=load_fixture("channel_response_snippet.json"),
62+
),
63+
match_querystring=True,
64+
)
65+
async with aiohttp.ClientSession() as session:
66+
youtube = YouTube(session=session)
67+
channel_generator = youtube.get_user_channels()
68+
channel = await channel_generator.__anext__()
69+
assert channel
70+
assert channel.snippet
71+
assert channel.snippet.published_at == datetime(
72+
2007,
73+
8,
74+
23,
75+
0,
76+
34,
77+
43,
78+
tzinfo=timezone.utc,
79+
)
80+
with pytest.raises(StopAsyncIteration):
81+
await channel_generator.__anext__()
82+
await youtube.close()

tests/test_video.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
from youtubeaio.youtube import YouTube
1111

1212
from . import load_fixture
13-
14-
YOUTUBE_URL = "youtube.googleapis.com"
13+
from .const import YOUTUBE_URL
1514

1615

1716
async def test_fetch_video(

tests/test_youtube.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@
1515
from youtubeaio.youtube import YouTube
1616

1717
from . import load_fixture
18-
19-
YOUTUBE_URL = "youtube.googleapis.com"
18+
from .const import YOUTUBE_URL
2019

2120

2221
async def test_new_session(

0 commit comments

Comments
 (0)