diff --git a/tests/asyncio/test_message.py b/tests/asyncio/test_message.py index 79b34bc..b4260cf 100644 --- a/tests/asyncio/test_message.py +++ b/tests/asyncio/test_message.py @@ -1,3 +1,5 @@ +from typing import Callable + import pytest from tests import assert_eventually_async @@ -191,9 +193,15 @@ async def test_batch_api_llm_async(async_qstash: AsyncQStash) -> None: @pytest.mark.asyncio -async def test_enqueue_async(async_qstash: AsyncQStash) -> None: +async def test_enqueue_async( + async_qstash: AsyncQStash, + cleanup_queue_async: Callable[[AsyncQStash, str], None], +) -> None: + name = "test_queue" + cleanup_queue_async(async_qstash, name) + res = await async_qstash.message.enqueue( - queue="test_queue", + queue=name, body="test-body", url="https://example.com", headers={ @@ -205,13 +213,17 @@ async def test_enqueue_async(async_qstash: AsyncQStash) -> None: assert len(res.message_id) > 0 - await async_qstash.queue.delete("test_queue") - @pytest.mark.asyncio -async def test_enqueue_json_async(async_qstash: AsyncQStash) -> None: +async def test_enqueue_json_async( + async_qstash: AsyncQStash, + cleanup_queue_async: Callable[[AsyncQStash, str], None], +) -> None: + name = "test_queue" + cleanup_queue_async(async_qstash, name) + res = await async_qstash.message.enqueue_json( - queue="test_queue", + queue=name, body={"test": "body"}, url="https://example.com", headers={ @@ -223,13 +235,17 @@ async def test_enqueue_json_async(async_qstash: AsyncQStash) -> None: assert len(res.message_id) > 0 - await async_qstash.queue.delete("test_queue") - @pytest.mark.asyncio -async def test_enqueue_api_llm_async(async_qstash: AsyncQStash) -> None: +async def test_enqueue_api_llm_async( + async_qstash: AsyncQStash, + cleanup_queue_async: Callable[[AsyncQStash, str], None], +) -> None: + name = "test_queue" + cleanup_queue_async(async_qstash, name) + res = await async_qstash.message.enqueue_json( - queue="test_queue", + queue=name, body={ "model": "meta-llama/Meta-Llama-3-8B-Instruct", "messages": [ @@ -247,8 +263,6 @@ async def test_enqueue_api_llm_async(async_qstash: AsyncQStash) -> None: assert len(res.message_id) > 0 - await async_qstash.queue.delete("test_queue") - @pytest.mark.asyncio async def test_publish_to_url_group_async(async_qstash: AsyncQStash) -> None: @@ -273,3 +287,17 @@ async def test_publish_to_url_group_async(async_qstash: AsyncQStash) -> None: await assert_delivered_eventually_async(async_qstash, res[0].message_id) await assert_delivered_eventually_async(async_qstash, res[1].message_id) + + +@pytest.mark.asyncio +async def test_timeout_async(async_qstash: AsyncQStash) -> None: + res = await async_qstash.message.publish_json( + body={"ex_key": "ex_value"}, + url="https://example.com", + timeout=90, + ) + + assert isinstance(res, PublishResponse) + assert len(res.message_id) > 0 + + await assert_delivered_eventually_async(async_qstash, res.message_id) diff --git a/tests/asyncio/test_queue.py b/tests/asyncio/test_queue.py index e14ed48..a511c9d 100644 --- a/tests/asyncio/test_queue.py +++ b/tests/asyncio/test_queue.py @@ -1,25 +1,68 @@ +from typing import Callable + import pytest + from upstash_qstash import AsyncQStash @pytest.mark.asyncio -async def test_queue_async(async_qstash: AsyncQStash) -> None: - await async_qstash.queue.upsert(queue="test_queue", parallelism=1) +async def test_queue_async( + async_qstash: AsyncQStash, + cleanup_queue_async: Callable[[AsyncQStash, str], None], +) -> None: + name = "test_queue" + cleanup_queue_async(async_qstash, name) - queue = await async_qstash.queue.get("test_queue") - assert queue.name == "test_queue" + await async_qstash.queue.upsert(queue=name, parallelism=1) + + queue = await async_qstash.queue.get(name) + assert queue.name == name assert queue.parallelism == 1 - await async_qstash.queue.upsert(queue="test_queue", parallelism=2) + await async_qstash.queue.upsert(queue=name, parallelism=2) - queue = await async_qstash.queue.get("test_queue") - assert queue.name == "test_queue" + queue = await async_qstash.queue.get(name) + assert queue.name == name assert queue.parallelism == 2 all_queues = await async_qstash.queue.list() - assert any(True for q in all_queues if q.name == "test_queue") + assert any(True for q in all_queues if q.name == name) - await async_qstash.queue.delete("test_queue") + await async_qstash.queue.delete(name) all_queues = await async_qstash.queue.list() - assert not any(True for q in all_queues if q.name == "test_queue") + assert not any(True for q in all_queues if q.name == name) + + +@pytest.mark.asyncio +async def test_queue_pause_resume_async( + async_qstash: AsyncQStash, + cleanup_queue_async: Callable[[AsyncQStash, str], None], +) -> None: + name = "test_queue" + cleanup_queue_async(async_qstash, name) + + await async_qstash.queue.upsert(queue=name) + + queue = await async_qstash.queue.get(name) + assert queue.paused is False + + await async_qstash.queue.pause(name) + + queue = await async_qstash.queue.get(name) + assert queue.paused is True + + await async_qstash.queue.resume(name) + + queue = await async_qstash.queue.get(name) + assert queue.paused is False + + await async_qstash.queue.upsert(name, paused=True) + + queue = await async_qstash.queue.get(name) + assert queue.paused is True + + await async_qstash.queue.upsert(name, paused=False) + + queue = await async_qstash.queue.get(name) + assert queue.paused is False diff --git a/tests/asyncio/test_schedules.py b/tests/asyncio/test_schedules.py index 3a6608f..6330844 100644 --- a/tests/asyncio/test_schedules.py +++ b/tests/asyncio/test_schedules.py @@ -1,25 +1,64 @@ +from typing import Callable + import pytest + from upstash_qstash import AsyncQStash @pytest.mark.asyncio -async def test_schedule_lifecycle_async(async_qstash: AsyncQStash) -> None: - sched_id = await async_qstash.schedule.create_json( - cron="* * * * *", +async def test_schedule_lifecycle_async( + async_qstash: AsyncQStash, + cleanup_schedule_async: Callable[[AsyncQStash, str], None], +) -> None: + schedule_id = await async_qstash.schedule.create_json( + cron="1 1 1 1 1", destination="https://example.com", body={"ex_key": "ex_value"}, ) - assert len(sched_id) > 0 + cleanup_schedule_async(async_qstash, schedule_id) - res = await async_qstash.schedule.get(sched_id) - assert res.schedule_id == sched_id - assert res.cron == "* * * * *" + assert len(schedule_id) > 0 + + res = await async_qstash.schedule.get(schedule_id) + assert res.schedule_id == schedule_id + assert res.cron == "1 1 1 1 1" list_res = await async_qstash.schedule.list() - assert any(s.schedule_id == sched_id for s in list_res) + assert any(s.schedule_id == schedule_id for s in list_res) - await async_qstash.schedule.delete(sched_id) + await async_qstash.schedule.delete(schedule_id) list_res = await async_qstash.schedule.list() - assert not any(s.schedule_id == sched_id for s in list_res) + assert not any(s.schedule_id == schedule_id for s in list_res) + + +@pytest.mark.asyncio +async def test_schedule_pause_resume_async( + async_qstash: AsyncQStash, + cleanup_schedule_async: Callable[[AsyncQStash, str], None], +) -> None: + schedule_id = await async_qstash.schedule.create_json( + cron="1 1 1 1 1", + destination="https://example.com", + body={"ex_key": "ex_value"}, + ) + + cleanup_schedule_async(async_qstash, schedule_id) + + assert len(schedule_id) > 0 + + res = await async_qstash.schedule.get(schedule_id) + assert res.schedule_id == schedule_id + assert res.cron == "1 1 1 1 1" + assert res.paused is False + + await async_qstash.schedule.pause(schedule_id) + + res = await async_qstash.schedule.get(schedule_id) + assert res.paused is True + + await async_qstash.schedule.resume(schedule_id) + + res = await async_qstash.schedule.get(schedule_id) + assert res.paused is False diff --git a/tests/conftest.py b/tests/conftest.py index d8bb3cd..2ee003d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,6 @@ +import asyncio +from typing import Callable + import pytest import pytest_asyncio @@ -13,3 +16,89 @@ def qstash(): @pytest_asyncio.fixture async def async_qstash(): return AsyncQStash(token=QSTASH_TOKEN) + + +@pytest.fixture +def cleanup_queue(request: pytest.FixtureRequest) -> Callable[[QStash, str], None]: + queue_names = [] + + def register(qstash: QStash, queue_name: str) -> None: + queue_names.append((qstash, queue_name)) + + def delete(): + for qstash, queue_name in queue_names: + try: + qstash.queue.delete(queue_name) + except Exception: + pass + + request.addfinalizer(delete) + + return register + + +@pytest_asyncio.fixture +def cleanup_queue_async( + request: pytest.FixtureRequest, +) -> Callable[[AsyncQStash, str], None]: + queue_names = [] + + def register(async_qstash: AsyncQStash, queue_name: str) -> None: + queue_names.append((async_qstash, queue_name)) + + def finalizer(): + async def delete(): + for async_qstash, queue_name in queue_names: + try: + await async_qstash.queue.delete(queue_name) + except Exception: + pass + + asyncio.run(delete()) + + request.addfinalizer(finalizer) + + return register + + +@pytest.fixture +def cleanup_schedule(request: pytest.FixtureRequest) -> Callable[[QStash, str], None]: + schedule_ids = [] + + def register(qstash: QStash, schedule_id: str) -> None: + schedule_ids.append((qstash, schedule_id)) + + def delete(): + for qstash, schedule_id in schedule_ids: + try: + qstash.schedule.delete(schedule_id) + except Exception: + pass + + request.addfinalizer(delete) + + return register + + +@pytest_asyncio.fixture +def cleanup_schedule_async( + request: pytest.FixtureRequest, +) -> Callable[[AsyncQStash, str], None]: + schedule_ids = [] + + def register(async_qstash: AsyncQStash, schedule_id: str) -> None: + schedule_ids.append((async_qstash, schedule_id)) + + def finalizer(): + async def delete(): + for async_qstash, schedule_id in schedule_ids: + try: + await async_qstash.schedule.delete(schedule_id) + except Exception: + pass + + asyncio.run(delete()) + + request.addfinalizer(finalizer) + + return register diff --git a/tests/test_message.py b/tests/test_message.py index 9187a17..43bf3a9 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -1,3 +1,5 @@ +from typing import Callable + import pytest from tests import assert_eventually @@ -179,9 +181,15 @@ def test_batch_api_llm(qstash: QStash) -> None: assert_delivered_eventually(qstash, res[0].message_id) -def test_enqueue(qstash: QStash) -> None: +def test_enqueue( + qstash: QStash, + cleanup_queue: Callable[[QStash, str], None], +) -> None: + name = "test_queue" + cleanup_queue(qstash, name) + res = qstash.message.enqueue( - queue="test_queue", + queue=name, body="test-body", url="https://example.com", headers={ @@ -193,12 +201,16 @@ def test_enqueue(qstash: QStash) -> None: assert len(res.message_id) > 0 - qstash.queue.delete("test_queue") +def test_enqueue_json( + qstash: QStash, + cleanup_queue: Callable[[QStash, str], None], +) -> None: + name = "test_queue" + cleanup_queue(qstash, name) -def test_enqueue_json(qstash: QStash) -> None: res = qstash.message.enqueue_json( - queue="test_queue", + queue=name, body={"test": "body"}, url="https://example.com", headers={ @@ -210,12 +222,16 @@ def test_enqueue_json(qstash: QStash) -> None: assert len(res.message_id) > 0 - qstash.queue.delete("test_queue") +def test_enqueue_api_llm( + qstash: QStash, + cleanup_queue: Callable[[QStash, str], None], +) -> None: + name = "test_queue" + cleanup_queue(qstash, name) -def test_enqueue_api_llm(qstash: QStash) -> None: res = qstash.message.enqueue_json( - queue="test_queue", + queue=name, body={ "model": "meta-llama/Meta-Llama-3-8B-Instruct", "messages": [ @@ -233,8 +249,6 @@ def test_enqueue_api_llm(qstash: QStash) -> None: assert len(res.message_id) > 0 - qstash.queue.delete("test_queue") - def test_publish_to_url_group(qstash: QStash) -> None: name = "python_url_group" @@ -258,3 +272,16 @@ def test_publish_to_url_group(qstash: QStash) -> None: assert_delivered_eventually(qstash, res[0].message_id) assert_delivered_eventually(qstash, res[1].message_id) + + +def test_timeout(qstash: QStash) -> None: + res = qstash.message.publish_json( + body={"ex_key": "ex_value"}, + url="https://example.com", + timeout=90, + ) + + assert isinstance(res, PublishResponse) + assert len(res.message_id) > 0 + + assert_delivered_eventually(qstash, res.message_id) diff --git a/tests/test_queue.py b/tests/test_queue.py index b1d4500..7ba8a4a 100644 --- a/tests/test_queue.py +++ b/tests/test_queue.py @@ -1,23 +1,64 @@ +from typing import Callable + from upstash_qstash import QStash -def test_queue(qstash: QStash) -> None: - qstash.queue.upsert(queue="test_queue", parallelism=1) +def test_queue( + qstash: QStash, + cleanup_queue: Callable[[QStash, str], None], +) -> None: + name = "test_queue" + cleanup_queue(qstash, name) + + qstash.queue.upsert(queue=name, parallelism=1) - queue = qstash.queue.get("test_queue") - assert queue.name == "test_queue" + queue = qstash.queue.get(name) + assert queue.name == name assert queue.parallelism == 1 - qstash.queue.upsert(queue="test_queue", parallelism=2) + qstash.queue.upsert(queue=name, parallelism=2) - queue = qstash.queue.get("test_queue") - assert queue.name == "test_queue" + queue = qstash.queue.get(name) + assert queue.name == name assert queue.parallelism == 2 all_queues = qstash.queue.list() - assert any(True for q in all_queues if q.name == "test_queue") + assert any(True for q in all_queues if q.name == name) - qstash.queue.delete("test_queue") + qstash.queue.delete(name) all_queues = qstash.queue.list() - assert not any(True for q in all_queues if q.name == "test_queue") + assert not any(True for q in all_queues if q.name == name) + + +def test_queue_pause_resume( + qstash: QStash, + cleanup_queue: Callable[[QStash, str], None], +) -> None: + name = "test_queue" + cleanup_queue(qstash, name) + + qstash.queue.upsert(queue=name) + + queue = qstash.queue.get(name) + assert queue.paused is False + + qstash.queue.pause(name) + + queue = qstash.queue.get(name) + assert queue.paused is True + + qstash.queue.resume(name) + + queue = qstash.queue.get(name) + assert queue.paused is False + + qstash.queue.upsert(name, paused=True) + + queue = qstash.queue.get(name) + assert queue.paused is True + + qstash.queue.upsert(name, paused=False) + + queue = qstash.queue.get(name) + assert queue.paused is False diff --git a/tests/test_schedules.py b/tests/test_schedules.py index bfde742..28170ca 100644 --- a/tests/test_schedules.py +++ b/tests/test_schedules.py @@ -1,23 +1,81 @@ +from typing import Callable + +import pytest + from upstash_qstash import QStash -def test_schedule_lifecycle(qstash: QStash) -> None: - sched_id = qstash.schedule.create_json( - cron="* * * * *", +@pytest.fixture +def cleanup_schedule(request: pytest.FixtureRequest) -> Callable[[QStash, str], None]: + schedule_ids = [] + + def register(qstash: QStash, schedule_id: str) -> None: + schedule_ids.append((qstash, schedule_id)) + + def delete(): + for qstash, schedule_id in schedule_ids: + try: + qstash.schedule.delete(schedule_id) + except Exception: + pass + + request.addfinalizer(delete) + + return register + + +def test_schedule_lifecycle( + qstash: QStash, + cleanup_schedule: Callable[[QStash, str], None], +) -> None: + schedule_id = qstash.schedule.create_json( + cron="1 1 1 1 1", destination="https://example.com", body={"ex_key": "ex_value"}, ) - assert len(sched_id) > 0 + cleanup_schedule(qstash, schedule_id) + + assert len(schedule_id) > 0 - res = qstash.schedule.get(sched_id) - assert res.schedule_id == sched_id - assert res.cron == "* * * * *" + res = qstash.schedule.get(schedule_id) + assert res.schedule_id == schedule_id + assert res.cron == "1 1 1 1 1" list_res = qstash.schedule.list() - assert any(s.schedule_id == sched_id for s in list_res) + assert any(s.schedule_id == schedule_id for s in list_res) - qstash.schedule.delete(sched_id) + qstash.schedule.delete(schedule_id) list_res = qstash.schedule.list() - assert not any(s.schedule_id == sched_id for s in list_res) + assert not any(s.schedule_id == schedule_id for s in list_res) + + +def test_schedule_pause_resume( + qstash: QStash, + cleanup_schedule: Callable[[QStash, str], None], +) -> None: + schedule_id = qstash.schedule.create_json( + cron="1 1 1 1 1", + destination="https://example.com", + body={"ex_key": "ex_value"}, + ) + + cleanup_schedule(qstash, schedule_id) + + assert len(schedule_id) > 0 + + res = qstash.schedule.get(schedule_id) + assert res.schedule_id == schedule_id + assert res.cron == "1 1 1 1 1" + assert res.paused is False + + qstash.schedule.pause(schedule_id) + + res = qstash.schedule.get(schedule_id) + assert res.paused is True + + qstash.schedule.resume(schedule_id) + + res = qstash.schedule.get(schedule_id) + assert res.paused is False diff --git a/upstash_qstash/asyncio/message.py b/upstash_qstash/asyncio/message.py index e920b95..6f45067 100644 --- a/upstash_qstash/asyncio/message.py +++ b/upstash_qstash/asyncio/message.py @@ -42,10 +42,11 @@ async def publish( retries: Optional[int] = None, callback: Optional[str] = None, failure_callback: Optional[str] = None, - delay: Optional[str] = None, + delay: Optional[Union[str, int]] = None, not_before: Optional[int] = None, deduplication_id: Optional[str] = None, content_based_deduplication: Optional[bool] = None, + timeout: Optional[Union[str, int]] = None, ) -> Union[PublishResponse, List[PublishUrlGroupResponse]]: """ Publishes a message to QStash. @@ -69,14 +70,20 @@ async def publish( :param callback: A callback url that will be called after each attempt. :param failure_callback: A failure callback url that will be called when a delivery is failed, that is when all the defined retries are exhausted. - :param delay: Delay the message delivery. The format is a number followed by duration - abbreviation, like `10s`. Available durations are `s` (seconds), `m` (minutes), - `h` (hours), and `d` (days). + :param delay: Delay the message delivery. The format for the delay string is a + number followed by duration abbreviation, like `10s`. Available durations + are `s` (seconds), `m` (minutes), `h` (hours), and `d` (days). As convenience, + it is also possible to specify the delay as an integer, which will be + interpreted as delay in seconds. :param not_before: Delay the message until a certain time in the future. The format is a unix timestamp in seconds, based on the UTC timezone. :param deduplication_id: Id to use while deduplicating messages. :param content_based_deduplication: Automatically deduplicate messages based on their content. + :param timeout: The HTTP timeout value to use while calling the destination URL. + When a timeout is specified, it will be used instead of the maximum timeout + value permitted by the QStash plan. It is useful in scenarios, where a message + should be delivered with a shorter timeout. """ destination = get_destination(url=url, url_group=url_group, api=api) @@ -91,6 +98,7 @@ async def publish( not_before=not_before, deduplication_id=deduplication_id, content_based_deduplication=content_based_deduplication, + timeout=timeout, ) response = await self._http.request( @@ -114,10 +122,11 @@ async def publish_json( retries: Optional[int] = None, callback: Optional[str] = None, failure_callback: Optional[str] = None, - delay: Optional[str] = None, + delay: Optional[Union[str, int]] = None, not_before: Optional[int] = None, deduplication_id: Optional[str] = None, content_based_deduplication: Optional[bool] = None, + timeout: Optional[Union[str, int]] = None, ) -> Union[PublishResponse, List[PublishUrlGroupResponse]]: """ Publish a message to QStash, automatically serializing the @@ -142,14 +151,20 @@ async def publish_json( :param callback: A callback url that will be called after each attempt. :param failure_callback: A failure callback url that will be called when a delivery is failed, that is when all the defined retries are exhausted. - :param delay: Delay the message delivery. The format is a number followed by duration - abbreviation, like `10s`. Available durations are `s` (seconds), `m` (minutes), - `h` (hours), and `d` (days). + :param delay: Delay the message delivery. The format for the delay string is a + number followed by duration abbreviation, like `10s`. Available durations + are `s` (seconds), `m` (minutes), `h` (hours), and `d` (days). As convenience, + it is also possible to specify the delay as an integer, which will be + interpreted as delay in seconds. :param not_before: Delay the message until a certain time in the future. The format is a unix timestamp in seconds, based on the UTC timezone. :param deduplication_id: Id to use while deduplicating messages. :param content_based_deduplication: Automatically deduplicate messages based on their content. + :param timeout: The HTTP timeout value to use while calling the destination URL. + When a timeout is specified, it will be used instead of the maximum timeout + value permitted by the QStash plan. It is useful in scenarios, where a message + should be delivered with a shorter timeout. """ return await self.publish( url=url, @@ -166,6 +181,7 @@ async def publish_json( not_before=not_before, deduplication_id=deduplication_id, content_based_deduplication=content_based_deduplication, + timeout=timeout, ) async def enqueue( @@ -182,10 +198,11 @@ async def enqueue( retries: Optional[int] = None, callback: Optional[str] = None, failure_callback: Optional[str] = None, - delay: Optional[str] = None, + delay: Optional[Union[str, int]] = None, not_before: Optional[int] = None, deduplication_id: Optional[str] = None, content_based_deduplication: Optional[bool] = None, + timeout: Optional[Union[str, int]] = None, ) -> Union[EnqueueResponse, List[EnqueueUrlGroupResponse]]: """ Enqueues a message, after creating the queue if it does @@ -211,14 +228,20 @@ async def enqueue( :param callback: A callback url that will be called after each attempt. :param failure_callback: A failure callback url that will be called when a delivery is failed, that is when all the defined retries are exhausted. - :param delay: Delay the message delivery. The format is a number followed by duration - abbreviation, like `10s`. Available durations are `s` (seconds), `m` (minutes), - `h` (hours), and `d` (days). + :param delay: Delay the message delivery. The format for the delay string is a + number followed by duration abbreviation, like `10s`. Available durations + are `s` (seconds), `m` (minutes), `h` (hours), and `d` (days). As convenience, + it is also possible to specify the delay as an integer, which will be + interpreted as delay in seconds. :param not_before: Delay the message until a certain time in the future. The format is a unix timestamp in seconds, based on the UTC timezone. :param deduplication_id: Id to use while deduplicating messages. :param content_based_deduplication: Automatically deduplicate messages based on their content. + :param timeout: The HTTP timeout value to use while calling the destination URL. + When a timeout is specified, it will be used instead of the maximum timeout + value permitted by the QStash plan. It is useful in scenarios, where a message + should be delivered with a shorter timeout. """ destination = get_destination(url=url, url_group=url_group, api=api) @@ -233,6 +256,7 @@ async def enqueue( not_before=not_before, deduplication_id=deduplication_id, content_based_deduplication=content_based_deduplication, + timeout=timeout, ) response = await self._http.request( @@ -257,10 +281,11 @@ async def enqueue_json( retries: Optional[int] = None, callback: Optional[str] = None, failure_callback: Optional[str] = None, - delay: Optional[str] = None, + delay: Optional[Union[str, int]] = None, not_before: Optional[int] = None, deduplication_id: Optional[str] = None, content_based_deduplication: Optional[bool] = None, + timeout: Optional[Union[str, int]] = None, ) -> Union[EnqueueResponse, List[EnqueueUrlGroupResponse]]: """ Enqueues a message, after creating the queue if it does @@ -287,14 +312,20 @@ async def enqueue_json( :param callback: A callback url that will be called after each attempt. :param failure_callback: A failure callback url that will be called when a delivery is failed, that is when all the defined retries are exhausted. - :param delay: Delay the message delivery. The format is a number followed by duration - abbreviation, like `10s`. Available durations are `s` (seconds), `m` (minutes), - `h` (hours), and `d` (days). + :param delay: Delay the message delivery. The format for the delay string is a + number followed by duration abbreviation, like `10s`. Available durations + are `s` (seconds), `m` (minutes), `h` (hours), and `d` (days). As convenience, + it is also possible to specify the delay as an integer, which will be + interpreted as delay in seconds. :param not_before: Delay the message until a certain time in the future. The format is a unix timestamp in seconds, based on the UTC timezone. :param deduplication_id: Id to use while deduplicating messages. :param content_based_deduplication: Automatically deduplicate messages based on their content. + :param timeout: The HTTP timeout value to use while calling the destination URL. + When a timeout is specified, it will be used instead of the maximum timeout + value permitted by the QStash plan. It is useful in scenarios, where a message + should be delivered with a shorter timeout. """ return await self.enqueue( queue=queue, @@ -312,6 +343,7 @@ async def enqueue_json( not_before=not_before, deduplication_id=deduplication_id, content_based_deduplication=content_based_deduplication, + timeout=timeout, ) async def batch( diff --git a/upstash_qstash/asyncio/queue.py b/upstash_qstash/asyncio/queue.py index 1895521..ce98a65 100644 --- a/upstash_qstash/asyncio/queue.py +++ b/upstash_qstash/asyncio/queue.py @@ -8,14 +8,22 @@ class AsyncQueueApi: def __init__(self, http: AsyncHttpClient) -> None: self._http = http - async def upsert(self, queue: str, *, parallelism: int = 1) -> None: + async def upsert( + self, + queue: str, + *, + parallelism: int = 1, + paused: bool = False, + ) -> None: """ Updates or creates a queue. :param queue: The name of the queue. :param parallelism: The number of parallel consumers consuming from the queue. + :param paused: Whether to pause the queue or not. A paused queue will not + deliver new messages until it is resumed. """ - body = prepare_upsert_body(queue, parallelism) + body = prepare_upsert_body(queue, parallelism, paused) await self._http.request( path="/v2/queues", @@ -56,3 +64,26 @@ async def delete(self, queue: str) -> None: method="DELETE", parse_response=False, ) + + async def pause(self, queue: str) -> None: + """ + Pauses the queue. + + A paused queue will not deliver messages until + it is resumed. + """ + await self._http.request( + path=f"/v2/queues/{queue}/pause", + method="POST", + parse_response=False, + ) + + async def resume(self, queue: str) -> None: + """ + Resumes the queue. + """ + await self._http.request( + path=f"/v2/queues/{queue}/resume", + method="POST", + parse_response=False, + ) diff --git a/upstash_qstash/asyncio/schedule.py b/upstash_qstash/asyncio/schedule.py index 59eb7cb..eac894f 100644 --- a/upstash_qstash/asyncio/schedule.py +++ b/upstash_qstash/asyncio/schedule.py @@ -26,7 +26,8 @@ async def create( retries: Optional[int] = None, callback: Optional[str] = None, failure_callback: Optional[str] = None, - delay: Optional[str] = None, + delay: Optional[Union[str, int]] = None, + timeout: Optional[Union[str, int]] = None, ) -> str: """ Creates a schedule to send messages periodically. @@ -44,9 +45,15 @@ async def create( :param callback: A callback url that will be called after each attempt. :param failure_callback: A failure callback url that will be called when a delivery is failed, that is when all the defined retries are exhausted. - :param delay: Delay the message delivery. The format is a number followed by duration - abbreviation, like `10s`. Available durations are `s` (seconds), `m` (minutes), - `h` (hours), and `d` (days). + :param delay: Delay the message delivery. The format for the delay string is a + number followed by duration abbreviation, like `10s`. Available durations + are `s` (seconds), `m` (minutes), `h` (hours), and `d` (days). As convenience, + it is also possible to specify the delay as an integer, which will be + interpreted as delay in seconds. + :param timeout: The HTTP timeout value to use while calling the destination URL. + When a timeout is specified, it will be used instead of the maximum timeout + value permitted by the QStash plan. It is useful in scenarios, where a message + should be delivered with a shorter timeout. """ req_headers = prepare_schedule_headers( cron=cron, @@ -57,6 +64,7 @@ async def create( callback=callback, failure_callback=failure_callback, delay=delay, + timeout=timeout, ) response = await self._http.request( @@ -79,7 +87,8 @@ async def create_json( retries: Optional[int] = None, callback: Optional[str] = None, failure_callback: Optional[str] = None, - delay: Optional[str] = None, + delay: Optional[Union[str, int]] = None, + timeout: Optional[Union[str, int]] = None, ) -> str: """ Creates a schedule to send messages periodically, automatically serializing the @@ -98,9 +107,15 @@ async def create_json( :param callback: A callback url that will be called after each attempt. :param failure_callback: A failure callback url that will be called when a delivery is failed, that is when all the defined retries are exhausted. - :param delay: Delay the message delivery. The format is a number followed by duration - abbreviation, like `10s`. Available durations are `s` (seconds), `m` (minutes), - `h` (hours), and `d` (days). + :param delay: Delay the message delivery. The format for the delay string is a + number followed by duration abbreviation, like `10s`. Available durations + are `s` (seconds), `m` (minutes), `h` (hours), and `d` (days). As convenience, + it is also possible to specify the delay as an integer, which will be + interpreted as delay in seconds. + :param timeout: The HTTP timeout value to use while calling the destination URL. + When a timeout is specified, it will be used instead of the maximum timeout + value permitted by the QStash plan. It is useful in scenarios, where a message + should be delivered with a shorter timeout. """ return await self.create( destination=destination, @@ -146,3 +161,26 @@ async def delete(self, schedule_id: str) -> None: method="DELETE", parse_response=False, ) + + async def pause(self, schedule_id: str) -> None: + """ + Pauses the schedule. + + A paused schedule will not produce new messages until + it is resumed. + """ + await self._http.request( + path=f"/v2/schedules/{schedule_id}/pause", + method="PATCH", + parse_response=False, + ) + + async def resume(self, schedule_id: str) -> None: + """ + Resumes the schedule. + """ + await self._http.request( + path=f"/v2/schedules/{schedule_id}/resume", + method="PATCH", + parse_response=False, + ) diff --git a/upstash_qstash/message.py b/upstash_qstash/message.py index eaaf8b0..577e4c8 100644 --- a/upstash_qstash/message.py +++ b/upstash_qstash/message.py @@ -119,17 +119,21 @@ class BatchRequest(TypedDict, total=False): that is when all the defined retries are exhausted. """ - delay: str + delay: Union[str, int] """ - Delay the message delivery. - The format is a number followed by duration abbreviation, - like `10s`. Available durations are `s` (seconds), `m` (minutes), - `h` (hours), and `d` (days). + Delay the message delivery. + + The format for the delay string is a + number followed by duration abbreviation, like `10s`. Available durations + are `s` (seconds), `m` (minutes), `h` (hours), and `d` (days). As convenience, + it is also possible to specify the delay as an integer, which will be + interpreted as delay in seconds. """ not_before: int """ Delay the message until a certain time in the future. + The format is a unix timestamp in seconds, based on the UTC timezone. """ @@ -139,6 +143,19 @@ class BatchRequest(TypedDict, total=False): content_based_deduplication: bool """Automatically deduplicate messages based on their content.""" + timeout: Union[str, int] + """ + The HTTP timeout value to use while calling the destination URL. + When a timeout is specified, it will be used instead of the maximum timeout + value permitted by the QStash plan. It is useful in scenarios, where a message + should be delivered with a shorter timeout. + + The format for the timeout string is a number followed by duration abbreviation, + like `10s`. Available durations are `s` (seconds), `m` (minutes), `h` (hours), + and `d` (days). As convenience, it is also possible to specify the timeout as + an integer, which will be interpreted as timeout in seconds. + """ + class BatchJsonRequest(TypedDict, total=False): queue: str @@ -180,17 +197,21 @@ class BatchJsonRequest(TypedDict, total=False): that is when all the defined retries are exhausted. """ - delay: str + delay: Union[str, int] """ - Delay the message delivery. - The format is a number followed by duration abbreviation, - like `10s`. Available durations are `s` (seconds), `m` (minutes), - `h` (hours), and `d` (days). + Delay the message delivery. + + The format for the delay string is a + number followed by duration abbreviation, like `10s`. Available durations + are `s` (seconds), `m` (minutes), `h` (hours), and `d` (days). As convenience, + it is also possible to specify the delay as an integer, which will be + interpreted as delay in seconds. """ not_before: int """ Delay the message until a certain time in the future. + The format is a unix timestamp in seconds, based on the UTC timezone. """ @@ -200,6 +221,19 @@ class BatchJsonRequest(TypedDict, total=False): content_based_deduplication: bool """Automatically deduplicate messages based on their content.""" + timeout: Union[str, int] + """ + The HTTP timeout value to use while calling the destination URL. + When a timeout is specified, it will be used instead of the maximum timeout + value permitted by the QStash plan. It is useful in scenarios, where a message + should be delivered with a shorter timeout. + + The format for the timeout string is a number followed by duration abbreviation, + like `10s`. Available durations are `s` (seconds), `m` (minutes), `h` (hours), + and `d` (days). As convenience, it is also possible to specify the timeout as + an integer, which will be interpreted as timeout in seconds. + """ + @dataclasses.dataclass class Message: @@ -301,10 +335,11 @@ def prepare_headers( retries: Optional[int], callback: Optional[str], failure_callback: Optional[str], - delay: Optional[str], + delay: Optional[Union[str, int]], not_before: Optional[int], deduplication_id: Optional[str], content_based_deduplication: Optional[bool], + timeout: Optional[Union[str, int]], ) -> Dict[str, str]: h = {} @@ -331,7 +366,10 @@ def prepare_headers( h["Upstash-Failure-Callback"] = failure_callback if delay is not None: - h["Upstash-Delay"] = delay + if isinstance(delay, int): + h["Upstash-Delay"] = f"{delay}s" + else: + h["Upstash-Delay"] = delay if not_before is not None: h["Upstash-Not-Before"] = str(not_before) @@ -342,6 +380,12 @@ def prepare_headers( if content_based_deduplication is not None: h["Upstash-Content-Based-Deduplication"] = str(content_based_deduplication) + if timeout is not None: + if isinstance(timeout, int): + h["Upstash-Timeout"] = f"{timeout}s" + else: + h["Upstash-Timeout"] = timeout + return h @@ -410,6 +454,7 @@ def prepare_batch_message_body(messages: List[BatchRequest]) -> str: not_before=msg.get("not_before"), deduplication_id=msg.get("deduplication_id"), content_based_deduplication=msg.get("content_based_deduplication"), + timeout=msg.get("timeout"), ) batch_messages.append( @@ -504,6 +549,9 @@ def convert_to_batch_messages( "content_based_deduplication" ] + if "timeout" in msg: + batch_msg["timeout"] = msg["timeout"] + batch_messages.append(batch_msg) return batch_messages @@ -548,10 +596,11 @@ def publish( retries: Optional[int] = None, callback: Optional[str] = None, failure_callback: Optional[str] = None, - delay: Optional[str] = None, + delay: Optional[Union[str, int]] = None, not_before: Optional[int] = None, deduplication_id: Optional[str] = None, content_based_deduplication: Optional[bool] = None, + timeout: Optional[Union[str, int]] = None, ) -> Union[PublishResponse, List[PublishUrlGroupResponse]]: """ Publishes a message to QStash. @@ -575,14 +624,20 @@ def publish( :param callback: A callback url that will be called after each attempt. :param failure_callback: A failure callback url that will be called when a delivery is failed, that is when all the defined retries are exhausted. - :param delay: Delay the message delivery. The format is a number followed by duration - abbreviation, like `10s`. Available durations are `s` (seconds), `m` (minutes), - `h` (hours), and `d` (days). + :param delay: Delay the message delivery. The format for the delay string is a + number followed by duration abbreviation, like `10s`. Available durations + are `s` (seconds), `m` (minutes), `h` (hours), and `d` (days). As convenience, + it is also possible to specify the delay as an integer, which will be + interpreted as delay in seconds. :param not_before: Delay the message until a certain time in the future. The format is a unix timestamp in seconds, based on the UTC timezone. :param deduplication_id: Id to use while deduplicating messages. :param content_based_deduplication: Automatically deduplicate messages based on their content. + :param timeout: The HTTP timeout value to use while calling the destination URL. + When a timeout is specified, it will be used instead of the maximum timeout + value permitted by the QStash plan. It is useful in scenarios, where a message + should be delivered with a shorter timeout. """ destination = get_destination(url=url, url_group=url_group, api=api) @@ -597,6 +652,7 @@ def publish( not_before=not_before, deduplication_id=deduplication_id, content_based_deduplication=content_based_deduplication, + timeout=timeout, ) response = self._http.request( @@ -620,10 +676,11 @@ def publish_json( retries: Optional[int] = None, callback: Optional[str] = None, failure_callback: Optional[str] = None, - delay: Optional[str] = None, + delay: Optional[Union[str, int]] = None, not_before: Optional[int] = None, deduplication_id: Optional[str] = None, content_based_deduplication: Optional[bool] = None, + timeout: Optional[Union[str, int]] = None, ) -> Union[PublishResponse, List[PublishUrlGroupResponse]]: """ Publish a message to QStash, automatically serializing the @@ -648,14 +705,20 @@ def publish_json( :param callback: A callback url that will be called after each attempt. :param failure_callback: A failure callback url that will be called when a delivery is failed, that is when all the defined retries are exhausted. - :param delay: Delay the message delivery. The format is a number followed by duration - abbreviation, like `10s`. Available durations are `s` (seconds), `m` (minutes), - `h` (hours), and `d` (days). + :param delay: Delay the message delivery. The format for the delay string is a + number followed by duration abbreviation, like `10s`. Available durations + are `s` (seconds), `m` (minutes), `h` (hours), and `d` (days). As convenience, + it is also possible to specify the delay as an integer, which will be + interpreted as delay in seconds. :param not_before: Delay the message until a certain time in the future. The format is a unix timestamp in seconds, based on the UTC timezone. :param deduplication_id: Id to use while deduplicating messages. :param content_based_deduplication: Automatically deduplicate messages based on their content. + :param timeout: The HTTP timeout value to use while calling the destination URL. + When a timeout is specified, it will be used instead of the maximum timeout + value permitted by the QStash plan. It is useful in scenarios, where a message + should be delivered with a shorter timeout. """ return self.publish( url=url, @@ -672,6 +735,7 @@ def publish_json( not_before=not_before, deduplication_id=deduplication_id, content_based_deduplication=content_based_deduplication, + timeout=timeout, ) def enqueue( @@ -688,10 +752,11 @@ def enqueue( retries: Optional[int] = None, callback: Optional[str] = None, failure_callback: Optional[str] = None, - delay: Optional[str] = None, + delay: Optional[Union[str, int]] = None, not_before: Optional[int] = None, deduplication_id: Optional[str] = None, content_based_deduplication: Optional[bool] = None, + timeout: Optional[Union[str, int]] = None, ) -> Union[EnqueueResponse, List[EnqueueUrlGroupResponse]]: """ Enqueues a message, after creating the queue if it does @@ -717,14 +782,20 @@ def enqueue( :param callback: A callback url that will be called after each attempt. :param failure_callback: A failure callback url that will be called when a delivery is failed, that is when all the defined retries are exhausted. - :param delay: Delay the message delivery. The format is a number followed by duration - abbreviation, like `10s`. Available durations are `s` (seconds), `m` (minutes), - `h` (hours), and `d` (days). + :param delay: Delay the message delivery. The format for the delay string is a + number followed by duration abbreviation, like `10s`. Available durations + are `s` (seconds), `m` (minutes), `h` (hours), and `d` (days). As convenience, + it is also possible to specify the delay as an integer, which will be + interpreted as delay in seconds. :param not_before: Delay the message until a certain time in the future. The format is a unix timestamp in seconds, based on the UTC timezone. :param deduplication_id: Id to use while deduplicating messages. :param content_based_deduplication: Automatically deduplicate messages based on their content. + :param timeout: The HTTP timeout value to use while calling the destination URL. + When a timeout is specified, it will be used instead of the maximum timeout + value permitted by the QStash plan. It is useful in scenarios, where a message + should be delivered with a shorter timeout. """ destination = get_destination(url=url, url_group=url_group, api=api) @@ -739,6 +810,7 @@ def enqueue( not_before=not_before, deduplication_id=deduplication_id, content_based_deduplication=content_based_deduplication, + timeout=timeout, ) response = self._http.request( @@ -763,10 +835,11 @@ def enqueue_json( retries: Optional[int] = None, callback: Optional[str] = None, failure_callback: Optional[str] = None, - delay: Optional[str] = None, + delay: Optional[Union[str, int]] = None, not_before: Optional[int] = None, deduplication_id: Optional[str] = None, content_based_deduplication: Optional[bool] = None, + timeout: Optional[Union[str, int]] = None, ) -> Union[EnqueueResponse, List[EnqueueUrlGroupResponse]]: """ Enqueues a message, after creating the queue if it does @@ -793,14 +866,20 @@ def enqueue_json( :param callback: A callback url that will be called after each attempt. :param failure_callback: A failure callback url that will be called when a delivery is failed, that is when all the defined retries are exhausted. - :param delay: Delay the message delivery. The format is a number followed by duration - abbreviation, like `10s`. Available durations are `s` (seconds), `m` (minutes), - `h` (hours), and `d` (days). + :param delay: Delay the message delivery. The format for the delay string is a + number followed by duration abbreviation, like `10s`. Available durations + are `s` (seconds), `m` (minutes), `h` (hours), and `d` (days). As convenience, + it is also possible to specify the delay as an integer, which will be + interpreted as delay in seconds. :param not_before: Delay the message until a certain time in the future. The format is a unix timestamp in seconds, based on the UTC timezone. :param deduplication_id: Id to use while deduplicating messages. :param content_based_deduplication: Automatically deduplicate messages based on their content. + :param timeout: The HTTP timeout value to use while calling the destination URL. + When a timeout is specified, it will be used instead of the maximum timeout + value permitted by the QStash plan. It is useful in scenarios, where a message + should be delivered with a shorter timeout. """ return self.enqueue( queue=queue, @@ -818,6 +897,7 @@ def enqueue_json( not_before=not_before, deduplication_id=deduplication_id, content_based_deduplication=content_based_deduplication, + timeout=timeout, ) def batch( diff --git a/upstash_qstash/queue.py b/upstash_qstash/queue.py index 5d269ef..c70e119 100644 --- a/upstash_qstash/queue.py +++ b/upstash_qstash/queue.py @@ -22,12 +22,16 @@ class Queue: lag: int """The number of unprocessed messages that exist in the queue.""" + paused: bool + """Whether the queue is paused or not.""" -def prepare_upsert_body(queue: str, parallelism: int) -> str: + +def prepare_upsert_body(queue: str, parallelism: int, paused: bool) -> str: return json.dumps( { "queueName": queue, "parallelism": parallelism, + "paused": paused, } ) @@ -39,6 +43,7 @@ def parse_queue_response(response: Dict[str, Any]) -> Queue: created_at=response["createdAt"], updated_at=response["updatedAt"], lag=response["lag"], + paused=response["paused"], ) @@ -46,14 +51,22 @@ class QueueApi: def __init__(self, http: HttpClient) -> None: self._http = http - def upsert(self, queue: str, *, parallelism: int = 1) -> None: + def upsert( + self, + queue: str, + *, + parallelism: int = 1, + paused: bool = False, + ) -> None: """ Updates or creates a queue. :param queue: The name of the queue. :param parallelism: The number of parallel consumers consuming from the queue. + :param paused: Whether to pause the queue or not. A paused queue will not + deliver new messages until it is resumed. """ - body = prepare_upsert_body(queue, parallelism) + body = prepare_upsert_body(queue, parallelism, paused) self._http.request( path="/v2/queues", @@ -94,3 +107,26 @@ def delete(self, queue: str) -> None: method="DELETE", parse_response=False, ) + + def pause(self, queue: str) -> None: + """ + Pauses the queue. + + A paused queue will not deliver messages until + it is resumed. + """ + self._http.request( + path=f"/v2/queues/{queue}/pause", + method="POST", + parse_response=False, + ) + + def resume(self, queue: str) -> None: + """ + Resumes the queue. + """ + self._http.request( + path=f"/v2/queues/{queue}/resume", + method="POST", + parse_response=False, + ) diff --git a/upstash_qstash/schedule.py b/upstash_qstash/schedule.py index 2618910..d33919e 100644 --- a/upstash_qstash/schedule.py +++ b/upstash_qstash/schedule.py @@ -50,6 +50,9 @@ class Schedule: caller_ip: Optional[str] """IP address of the creator of this schedule.""" + paused: bool + """Whether the schedule is paused or not.""" + def prepare_schedule_headers( *, @@ -60,7 +63,8 @@ def prepare_schedule_headers( retries: Optional[int], callback: Optional[str], failure_callback: Optional[str], - delay: Optional[str], + delay: Optional[Union[str, int]], + timeout: Optional[Union[str, int]], ) -> Dict[str, str]: h = { "Upstash-Cron": cron, @@ -89,7 +93,16 @@ def prepare_schedule_headers( h["Upstash-Failure-Callback"] = failure_callback if delay is not None: - h["Upstash-Delay"] = delay + if isinstance(delay, int): + h["Upstash-Delay"] = f"{delay}s" + else: + h["Upstash-Delay"] = delay + + if timeout is not None: + if isinstance(timeout, int): + h["Upstash-Timeout"] = f"{timeout}s" + else: + h["Upstash-Timeout"] = timeout return h @@ -109,6 +122,7 @@ def parse_schedule_response(response: Dict[str, Any]) -> Schedule: failure_callback=response.get("failureCallback"), delay=response.get("delay"), caller_ip=response.get("callerIP"), + paused=response.get("isPaused", False), ) @@ -128,7 +142,8 @@ def create( retries: Optional[int] = None, callback: Optional[str] = None, failure_callback: Optional[str] = None, - delay: Optional[str] = None, + delay: Optional[Union[str, int]] = None, + timeout: Optional[Union[str, int]] = None, ) -> str: """ Creates a schedule to send messages periodically. @@ -146,9 +161,15 @@ def create( :param callback: A callback url that will be called after each attempt. :param failure_callback: A failure callback url that will be called when a delivery is failed, that is when all the defined retries are exhausted. - :param delay: Delay the message delivery. The format is a number followed by duration - abbreviation, like `10s`. Available durations are `s` (seconds), `m` (minutes), - `h` (hours), and `d` (days). + :param delay: Delay the message delivery. The format for the delay string is a + number followed by duration abbreviation, like `10s`. Available durations + are `s` (seconds), `m` (minutes), `h` (hours), and `d` (days). As convenience, + it is also possible to specify the delay as an integer, which will be + interpreted as delay in seconds. + :param timeout: The HTTP timeout value to use while calling the destination URL. + When a timeout is specified, it will be used instead of the maximum timeout + value permitted by the QStash plan. It is useful in scenarios, where a message + should be delivered with a shorter timeout. """ req_headers = prepare_schedule_headers( cron=cron, @@ -159,6 +180,7 @@ def create( callback=callback, failure_callback=failure_callback, delay=delay, + timeout=timeout, ) response = self._http.request( @@ -181,7 +203,8 @@ def create_json( retries: Optional[int] = None, callback: Optional[str] = None, failure_callback: Optional[str] = None, - delay: Optional[str] = None, + delay: Optional[Union[str, int]] = None, + timeout: Optional[Union[str, int]] = None, ) -> str: """ Creates a schedule to send messages periodically, automatically serializing the @@ -200,9 +223,15 @@ def create_json( :param callback: A callback url that will be called after each attempt. :param failure_callback: A failure callback url that will be called when a delivery is failed, that is when all the defined retries are exhausted. - :param delay: Delay the message delivery. The format is a number followed by duration - abbreviation, like `10s`. Available durations are `s` (seconds), `m` (minutes), - `h` (hours), and `d` (days). + :param delay: Delay the message delivery. The format for the delay string is a + number followed by duration abbreviation, like `10s`. Available durations + are `s` (seconds), `m` (minutes), `h` (hours), and `d` (days). As convenience, + it is also possible to specify the delay as an integer, which will be + interpreted as delay in seconds. + :param timeout: The HTTP timeout value to use while calling the destination URL. + When a timeout is specified, it will be used instead of the maximum timeout + value permitted by the QStash plan. It is useful in scenarios, where a message + should be delivered with a shorter timeout. """ return self.create( destination=destination, @@ -215,6 +244,7 @@ def create_json( callback=callback, failure_callback=failure_callback, delay=delay, + timeout=timeout, ) def get(self, schedule_id: str) -> Schedule: @@ -248,3 +278,26 @@ def delete(self, schedule_id: str) -> None: method="DELETE", parse_response=False, ) + + def pause(self, schedule_id: str) -> None: + """ + Pauses the schedule. + + A paused schedule will not produce new messages until + it is resumed. + """ + self._http.request( + path=f"/v2/schedules/{schedule_id}/pause", + method="PATCH", + parse_response=False, + ) + + def resume(self, schedule_id: str) -> None: + """ + Resumes the schedule. + """ + self._http.request( + path=f"/v2/schedules/{schedule_id}/resume", + method="PATCH", + parse_response=False, + )