Skip to content

Commit 2cfbad5

Browse files
committed
Add wrapper methods to allow using unique key only
1 parent ab6df5e commit 2cfbad5

File tree

2 files changed

+143
-4
lines changed

2 files changed

+143
-4
lines changed

src/apify_client/clients/resource_clients/request_queue.py

Lines changed: 141 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
import json as jsonlib
55
import logging
66
import math
7+
import re
8+
from base64 import b64encode
79
from collections.abc import Iterable
10+
from hashlib import sha256
811
from queue import Queue
912
from typing import TYPE_CHECKING, Any, TypedDict
1013

@@ -46,6 +49,32 @@ class BatchAddRequestsResult(TypedDict):
4649
unprocessedRequests: list[dict]
4750

4851

52+
def unique_key_to_request_id(unique_key: str, *, request_id_length: int = 15) -> str:
53+
"""Generate a deterministic request ID based on a unique key.
54+
55+
Args:
56+
unique_key: The unique key to convert into a request ID.
57+
request_id_length: The length of the request ID.
58+
59+
Returns:
60+
A URL-safe, truncated request ID based on the unique key.
61+
"""
62+
if not unique_key:
63+
raise ValueError('unique_key must not be empty')
64+
65+
# Encode the unique key and compute its SHA-256 hash
66+
hashed_key = sha256(unique_key.encode('utf-8')).digest()
67+
68+
# Encode the hash in base64 and decode it to get a string
69+
base64_encoded = b64encode(hashed_key).decode('utf-8')
70+
71+
# Remove characters that are not URL-safe ('+', '/', or '=')
72+
url_safe_key = re.sub(r'(\+|\/|=)', '', base64_encoded)
73+
74+
# Truncate the key to the desired length
75+
return url_safe_key[:request_id_length]
76+
77+
4978
class RequestQueueClient(ResourceClient):
5079
"""Sub-client for manipulating a single request queue."""
5180

@@ -194,6 +223,19 @@ def get_request(self, request_id: str) -> dict | None:
194223

195224
return None
196225

226+
def get_request_by_unique_key(self, request_unique_key: str) -> dict | None:
227+
"""Retrieve a request from the queue.
228+
229+
https://docs.apify.com/api/v2#/reference/request-queues/request/get-request
230+
231+
Args:
232+
request_unique_key: unique key of the request to retrieve.
233+
234+
Returns:
235+
The retrieved request, or None, if it did not exist.
236+
"""
237+
return self.get_request(unique_key_to_request_id(request_unique_key))
238+
197239
def update_request(self, request: dict, *, forefront: bool | None = None) -> dict:
198240
"""Update a request in the queue.
199241
@@ -206,7 +248,7 @@ def update_request(self, request: dict, *, forefront: bool | None = None) -> dic
206248
Returns:
207249
The updated request.
208250
"""
209-
request_id = request['id']
251+
request_id = request.get('id', unique_key_to_request_id(request.get('unique_key', '')))
210252

211253
request_params = self._params(forefront=forefront, clientKey=self.client_key)
212254

@@ -239,6 +281,16 @@ def delete_request(self, request_id: str) -> None:
239281
timeout_secs=_SMALL_TIMEOUT,
240282
)
241283

284+
def delete_request_by_unique_key(self, request_unique_key: str) -> None:
285+
"""Delete a request from the queue.
286+
287+
https://docs.apify.com/api/v2#/reference/request-queues/request/delete-request
288+
289+
Args:
290+
request_unique_key: Unique key of the request to delete.
291+
"""
292+
return self.delete_request(unique_key_to_request_id(request_unique_key))
293+
242294
def prolong_request_lock(
243295
self,
244296
request_id: str,
@@ -266,6 +318,26 @@ def prolong_request_lock(
266318

267319
return parse_date_fields(pluck_data(jsonlib.loads(response.text)))
268320

321+
def prolong_request_lock_by_unique_key(
322+
self,
323+
request_unique_key: str,
324+
*,
325+
forefront: bool | None = None,
326+
lock_secs: int,
327+
) -> dict:
328+
"""Prolong the lock on a request.
329+
330+
https://docs.apify.com/api/v2#/reference/request-queues/request-lock/prolong-request-lock
331+
332+
Args:
333+
request_unique_key: ID of the request to prolong the lock.
334+
forefront: Whether to put the request in the beginning or the end of the queue after lock expires.
335+
lock_secs: By how much to prolong the lock, in seconds.
336+
"""
337+
return self.prolong_request_lock(
338+
unique_key_to_request_id(request_unique_key), forefront=forefront, lock_secs=lock_secs
339+
)
340+
269341
def delete_request_lock(self, request_id: str, *, forefront: bool | None = None) -> None:
270342
"""Delete the lock on a request.
271343
@@ -284,6 +356,17 @@ def delete_request_lock(self, request_id: str, *, forefront: bool | None = None)
284356
timeout_secs=_SMALL_TIMEOUT,
285357
)
286358

359+
def delete_request_lock_by_unique_key(self, request_unique_key: str, *, forefront: bool | None = None) -> None:
360+
"""Delete the lock on a request.
361+
362+
https://docs.apify.com/api/v2#/reference/request-queues/request-lock/delete-request-lock
363+
364+
Args:
365+
request_unique_key: ID of the request to delete the lock.
366+
forefront: Whether to put the request in the beginning or the end of the queue after the lock is deleted.
367+
"""
368+
return self.delete_request_lock(unique_key_to_request_id(request_unique_key), forefront=forefront)
369+
287370
def batch_add_requests(
288371
self,
289372
requests: list[dict],
@@ -574,6 +657,19 @@ async def get_request(self, request_id: str) -> dict | None:
574657

575658
return None
576659

660+
async def get_request_by_unique_key(self, request_unique_key: str) -> dict | None:
661+
"""Retrieve a request from the queue.
662+
663+
https://docs.apify.com/api/v2#/reference/request-queues/request/get-request
664+
665+
Args:
666+
request_unique_key: unique key of the request to retrieve.
667+
668+
Returns:
669+
The retrieved request, or None, if it did not exist.
670+
"""
671+
return await self.get_request(unique_key_to_request_id(request_unique_key))
672+
577673
async def update_request(self, request: dict, *, forefront: bool | None = None) -> dict:
578674
"""Update a request in the queue.
579675
@@ -586,7 +682,7 @@ async def update_request(self, request: dict, *, forefront: bool | None = None)
586682
Returns:
587683
The updated request.
588684
"""
589-
request_id = request['id']
685+
request_id = request.get('id', unique_key_to_request_id(request.get('unique_key', '')))
590686

591687
request_params = self._params(forefront=forefront, clientKey=self.client_key)
592688

@@ -617,6 +713,16 @@ async def delete_request(self, request_id: str) -> None:
617713
timeout_secs=_SMALL_TIMEOUT,
618714
)
619715

716+
async def delete_request_by_unique_key(self, request_unique_key: str) -> None:
717+
"""Delete a request from the queue.
718+
719+
https://docs.apify.com/api/v2#/reference/request-queues/request/delete-request
720+
721+
Args:
722+
request_unique_key: Unique key of the request to delete.
723+
"""
724+
return await self.delete_request(unique_key_to_request_id(request_unique_key))
725+
620726
async def prolong_request_lock(
621727
self,
622728
request_id: str,
@@ -644,6 +750,26 @@ async def prolong_request_lock(
644750

645751
return parse_date_fields(pluck_data(jsonlib.loads(response.text)))
646752

753+
async def prolong_request_lock_by_unique_key(
754+
self,
755+
request_unique_key: str,
756+
*,
757+
forefront: bool | None = None,
758+
lock_secs: int,
759+
) -> dict:
760+
"""Prolong the lock on a request.
761+
762+
https://docs.apify.com/api/v2#/reference/request-queues/request-lock/prolong-request-lock
763+
764+
Args:
765+
request_unique_key: ID of the request to prolong the lock.
766+
forefront: Whether to put the request in the beginning or the end of the queue after lock expires.
767+
lock_secs: By how much to prolong the lock, in seconds.
768+
"""
769+
return await self.prolong_request_lock(
770+
unique_key_to_request_id(request_unique_key), forefront=forefront, lock_secs=lock_secs
771+
)
772+
647773
async def delete_request_lock(
648774
self,
649775
request_id: str,
@@ -667,6 +793,19 @@ async def delete_request_lock(
667793
timeout_secs=_SMALL_TIMEOUT,
668794
)
669795

796+
async def delete_request_lock_by_unique_key(
797+
self, request_unique_key: str, *, forefront: bool | None = None
798+
) -> None:
799+
"""Delete the lock on a request.
800+
801+
https://docs.apify.com/api/v2#/reference/request-queues/request-lock/delete-request-lock
802+
803+
Args:
804+
request_unique_key: ID of the request to delete the lock.
805+
forefront: Whether to put the request in the beginning or the end of the queue after the lock is deleted.
806+
"""
807+
return await self.delete_request_lock(unique_key_to_request_id(request_unique_key), forefront=forefront)
808+
670809
async def _batch_add_requests_worker(
671810
self,
672811
queue: asyncio.Queue[Iterable[dict]],

uv.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)