4
4
import json as jsonlib
5
5
import logging
6
6
import math
7
+ import re
8
+ from base64 import b64encode
7
9
from collections .abc import Iterable
10
+ from hashlib import sha256
8
11
from queue import Queue
9
12
from typing import TYPE_CHECKING , Any , TypedDict
10
13
@@ -46,6 +49,32 @@ class BatchAddRequestsResult(TypedDict):
46
49
unprocessedRequests : list [dict ]
47
50
48
51
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
+
49
78
class RequestQueueClient (ResourceClient ):
50
79
"""Sub-client for manipulating a single request queue."""
51
80
@@ -194,6 +223,19 @@ def get_request(self, request_id: str) -> dict | None:
194
223
195
224
return None
196
225
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
+
197
239
def update_request (self , request : dict , * , forefront : bool | None = None ) -> dict :
198
240
"""Update a request in the queue.
199
241
@@ -206,7 +248,7 @@ def update_request(self, request: dict, *, forefront: bool | None = None) -> dic
206
248
Returns:
207
249
The updated request.
208
250
"""
209
- request_id = request [ 'id' ]
251
+ request_id = request . get ( 'id' , unique_key_to_request_id ( request . get ( 'unique_key' , '' )))
210
252
211
253
request_params = self ._params (forefront = forefront , clientKey = self .client_key )
212
254
@@ -239,6 +281,16 @@ def delete_request(self, request_id: str) -> None:
239
281
timeout_secs = _SMALL_TIMEOUT ,
240
282
)
241
283
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
+
242
294
def prolong_request_lock (
243
295
self ,
244
296
request_id : str ,
@@ -266,6 +318,26 @@ def prolong_request_lock(
266
318
267
319
return parse_date_fields (pluck_data (jsonlib .loads (response .text )))
268
320
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
+
269
341
def delete_request_lock (self , request_id : str , * , forefront : bool | None = None ) -> None :
270
342
"""Delete the lock on a request.
271
343
@@ -284,6 +356,17 @@ def delete_request_lock(self, request_id: str, *, forefront: bool | None = None)
284
356
timeout_secs = _SMALL_TIMEOUT ,
285
357
)
286
358
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
+
287
370
def batch_add_requests (
288
371
self ,
289
372
requests : list [dict ],
@@ -574,6 +657,19 @@ async def get_request(self, request_id: str) -> dict | None:
574
657
575
658
return None
576
659
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
+
577
673
async def update_request (self , request : dict , * , forefront : bool | None = None ) -> dict :
578
674
"""Update a request in the queue.
579
675
@@ -586,7 +682,7 @@ async def update_request(self, request: dict, *, forefront: bool | None = None)
586
682
Returns:
587
683
The updated request.
588
684
"""
589
- request_id = request [ 'id' ]
685
+ request_id = request . get ( 'id' , unique_key_to_request_id ( request . get ( 'unique_key' , '' )))
590
686
591
687
request_params = self ._params (forefront = forefront , clientKey = self .client_key )
592
688
@@ -617,6 +713,16 @@ async def delete_request(self, request_id: str) -> None:
617
713
timeout_secs = _SMALL_TIMEOUT ,
618
714
)
619
715
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
+
620
726
async def prolong_request_lock (
621
727
self ,
622
728
request_id : str ,
@@ -644,6 +750,26 @@ async def prolong_request_lock(
644
750
645
751
return parse_date_fields (pluck_data (jsonlib .loads (response .text )))
646
752
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
+
647
773
async def delete_request_lock (
648
774
self ,
649
775
request_id : str ,
@@ -667,6 +793,19 @@ async def delete_request_lock(
667
793
timeout_secs = _SMALL_TIMEOUT ,
668
794
)
669
795
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
+
670
809
async def _batch_add_requests_worker (
671
810
self ,
672
811
queue : asyncio .Queue [Iterable [dict ]],
0 commit comments