From 5ed056f2da79767ba13a0889dd98de48ce847347 Mon Sep 17 00:00:00 2001 From: SeoulSKY Date: Mon, 2 Sep 2024 05:21:54 -0600 Subject: [PATCH] Fix implementation of unsubscribe() --- docs/changelog.rst | 18 ++++++++++++++++++ docs/conf.py | 2 +- examples/basic/async.py | 2 +- examples/multithreading.py | 2 +- setup.py | 2 +- tests/test_youtube_notifier.py | 4 ++++ ytnoti/__init__.py | 16 ++++++++-------- 7 files changed, 34 insertions(+), 12 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index e26a590..77ee580 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,24 @@ Changelog ========== +v2.1.0 +------ + +* From now on, YouTubeNotifier extends AsyncYouTubeNotifier and AsyncYouTubeNotifier extends object. BaseYouTubeNotifier was removed. +* Added (Async)YouTubeNotifier.run_in_background(). It works similar to the run method, but it immediately returns when the notifier is running. +* Added (Async) YouTubeNotifier.unsubscribe(). It unsubscribes the subscribed channel IDs +* From now on, (Async)YouTubeNotifier.subscribe() immediately raises ValueError when the given channel ids are not valid. In the past, it didn't raise error until the notifier started running. +* Improved the speed of verifying channel IDs + +Following methods are deprecated and will be removed in version 3.0.0 +* AsyncYouTubeNotifier.serve() -> use AsyncYouTubeNotifier.run() +* (Async)YouTubeNotifier.add_listener() -> use either add_any_listener(), add_upload_listener(), or add_edit_listener() + +Following decorators are deprecated and will be removed in version 3.0.0 +* @(Async)YouTubeNotifier.listener() -> use either @any, @upload or @edit + +**Full Changelog**: https://github.com/SeoulSKY/ytnoti/compare/v2.0.1...v2.1.0 + v2.0.1 ------ diff --git a/docs/conf.py b/docs/conf.py index 7aba899..b7ceccf 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,7 +17,7 @@ project = "ytnoti" copyright = "2024 - Present, SeoulSKY" # noqa: A001 author = "SeoulSKY" -release = "2.0.1" +release = "2.1.0" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/examples/basic/async.py b/examples/basic/async.py index 497d45b..ad2f4b4 100644 --- a/examples/basic/async.py +++ b/examples/basic/async.py @@ -21,7 +21,7 @@ async def listener(video: Video) -> None: print(f"New video from {video.channel.name}: {video.title}") await notifier.subscribe("UC9EEyg7QBL-stRX-7hTV3ng") # Channel ID of SpeedyStyle - await notifier.serve() + await notifier.run() if __name__ == "__main__": diff --git a/examples/multithreading.py b/examples/multithreading.py index f3c1f06..dbf6d28 100644 --- a/examples/multithreading.py +++ b/examples/multithreading.py @@ -1,4 +1,4 @@ -"""THe Following is an example of how to use this library with multithreading.""" +"""The following is an example of how to use this library with multithreading.""" import time from threading import Thread diff --git a/setup.py b/setup.py index cb0c791..606336f 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name="ytnoti", - version="2.0.1", + version="2.1.0", packages=find_packages(), author="SeoulSKY", author_email="contact@seoulsky.org", diff --git a/tests/test_youtube_notifier.py b/tests/test_youtube_notifier.py index 3703880..13fde45 100644 --- a/tests/test_youtube_notifier.py +++ b/tests/test_youtube_notifier.py @@ -126,11 +126,15 @@ def test_subscribe(notifier: YouTubeNotifier) -> None: def test_unsubscribe(notifier: YouTubeNotifier) -> None: """Test the unsubscribe method of the YouTubeNotifier class.""" + notifier.subscribe(channel_ids) notifier.unsubscribe(channel_ids) with pytest.raises(ValueError): notifier.unsubscribe("Invalid") + with pytest.raises(ValueError): + notifier.unsubscribe(channel_ids) + assert len(notifier._subscribed_ids) == 0 def test_listener(notifier: YouTubeNotifier) -> None: diff --git a/ytnoti/__init__.py b/ytnoti/__init__.py index 3923952..34f28ce 100644 --- a/ytnoti/__init__.py +++ b/ytnoti/__init__.py @@ -25,7 +25,7 @@ import warnings from asyncio import Task from collections.abc import Callable, Coroutine, Iterable -from contextlib import asynccontextmanager, contextmanager +from contextlib import asynccontextmanager, contextmanager, suppress from datetime import datetime from http import HTTPStatus from pyexpat import ExpatError @@ -38,6 +38,7 @@ from fastapi.routing import APIRoute from httpx import AsyncClient from pyngrok import ngrok +from pyngrok.exception import PyngrokNgrokURLError from starlette.routing import Route from uvicorn import Config, Server @@ -591,19 +592,17 @@ async def subscribe(self, channel_ids: str | Iterable[str]) -> Self: async def unsubscribe(self, channel_ids: str | Iterable[str]) -> Self: """Unsubscribe from YouTube channels to stop receiving push notifications. - This is lazy and will unsubscribe when the notifier is ready. - If the notifier is already ready, it will unsubscribe immediately. :param channel_ids: The channel ID(s) to unsubscribe from. :return: The current instance for method chaining. - :raises ValueError: If the channel ID is invalid. - :raises HTTPError: If failed to verify the channel ID or failed to unsubscribe - due to an HTTP error. + :raises ValueError: If the channel_ids includes ids that are not subscribed """ if isinstance(channel_ids, str): channel_ids = [channel_ids] - await self._verify_channel_ids(channel_ids) + if not self._subscribed_ids.issuperset(channel_ids): + raise ValueError(f"No such subscribed channel IDs: " + f"{self._subscribed_ids.difference(channel_ids)}") unsubscribe_ids = self._subscribed_ids.intersection(channel_ids) @@ -687,7 +686,8 @@ def stop(self) -> None: self._server = None if self._config.using_ngrok: - ngrok.disconnect(self._config.callback_url) + with suppress(PyngrokNgrokURLError): + ngrok.disconnect(self._config.callback_url) async def _on_exit(self) -> None: """Perform a task after the notifier is stopped."""