omnata-plugin-runtime 0.4.5__py3-none-any.whl → 0.4.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omnata_plugin_runtime/omnata_plugin.py +11 -0
 - omnata_plugin_runtime/rate_limiting.py +51 -5
 - {omnata_plugin_runtime-0.4.5.dist-info → omnata_plugin_runtime-0.4.6.dist-info}/METADATA +1 -1
 - {omnata_plugin_runtime-0.4.5.dist-info → omnata_plugin_runtime-0.4.6.dist-info}/RECORD +6 -6
 - {omnata_plugin_runtime-0.4.5.dist-info → omnata_plugin_runtime-0.4.6.dist-info}/LICENSE +0 -0
 - {omnata_plugin_runtime-0.4.5.dist-info → omnata_plugin_runtime-0.4.6.dist-info}/WHEEL +0 -0
 
| 
         @@ -21,6 +21,7 @@ import queue 
     | 
|
| 
       21 
21 
     | 
    
         
             
            import threading
         
     | 
| 
       22 
22 
     | 
    
         
             
            import time
         
     | 
| 
       23 
23 
     | 
    
         
             
            import hashlib
         
     | 
| 
      
 24 
     | 
    
         
            +
            import requests
         
     | 
| 
       24 
25 
     | 
    
         
             
            from abc import ABC, abstractmethod
         
     | 
| 
       25 
26 
     | 
    
         
             
            from decimal import Decimal
         
     | 
| 
       26 
27 
     | 
    
         
             
            from functools import partial, wraps, reduce
         
     | 
| 
         @@ -263,6 +264,16 @@ class SyncRequest(ABC): 
     | 
|
| 
       263 
264 
     | 
    
         
             
                    Takes into account the run deadline and cancellation status.
         
     | 
| 
       264 
265 
     | 
    
         
             
                    This is an alternative which can be used when the target API does not publish specific rate limits, and instead just asks you to respond to 429s as they are sent.
         
     | 
| 
       265 
266 
     | 
    
         
             
                    """
         
     | 
| 
      
 267 
     | 
    
         
            +
                    if self.test_replay_mode:
         
     | 
| 
      
 268 
     | 
    
         
            +
                        # when in test replay mode, we want to make the same requests but without any waiting
         
     | 
| 
      
 269 
     | 
    
         
            +
                        return RateLimitedSession(
         
     | 
| 
      
 270 
     | 
    
         
            +
                            run_deadline=self._run_deadline,
         
     | 
| 
      
 271 
     | 
    
         
            +
                            thread_cancellation_token=self._thread_cancellation_token,
         
     | 
| 
      
 272 
     | 
    
         
            +
                            max_retries=max_retries,
         
     | 
| 
      
 273 
     | 
    
         
            +
                            backoff_factor=0,
         
     | 
| 
      
 274 
     | 
    
         
            +
                            statuses_to_include=statuses_to_include,
         
     | 
| 
      
 275 
     | 
    
         
            +
                            respect_retry_after_header=False
         
     | 
| 
      
 276 
     | 
    
         
            +
                        )
         
     | 
| 
       266 
277 
     | 
    
         
             
                    return RateLimitedSession(
         
     | 
| 
       267 
278 
     | 
    
         
             
                        run_deadline=self._run_deadline,
         
     | 
| 
       268 
279 
     | 
    
         
             
                        thread_cancellation_token=self._thread_cancellation_token,
         
     | 
| 
         @@ -8,8 +8,9 @@ import re 
     | 
|
| 
       8 
8 
     | 
    
         
             
            import threading
         
     | 
| 
       9 
9 
     | 
    
         
             
            from email.utils import parsedate_to_datetime
         
     | 
| 
       10 
10 
     | 
    
         
             
            from logging import getLogger
         
     | 
| 
       11 
     | 
    
         
            -
            from typing import List, Literal, Optional, Dict, Tuple
         
     | 
| 
      
 11 
     | 
    
         
            +
            from typing import Any, List, Literal, Optional, Dict, Tuple
         
     | 
| 
       12 
12 
     | 
    
         
             
            import requests
         
     | 
| 
      
 13 
     | 
    
         
            +
            import time
         
     | 
| 
       13 
14 
     | 
    
         
             
            from pydantic import Field, root_validator
         
     | 
| 
       14 
15 
     | 
    
         
             
            from pydantic.json import pydantic_encoder
         
     | 
| 
       15 
16 
     | 
    
         
             
            from .configuration import SubscriptableBaseModel
         
     | 
| 
         @@ -349,6 +350,43 @@ class RetryLaterException(Exception): 
     | 
|
| 
       349 
350 
     | 
    
         
             
                    self.message = message
         
     | 
| 
       350 
351 
     | 
    
         
             
                    super().__init__(self.message)
         
     | 
| 
       351 
352 
     | 
    
         | 
| 
      
 353 
     | 
    
         
            +
            class RetryWithLogging(Retry):
         
     | 
| 
      
 354 
     | 
    
         
            +
                """
         
     | 
| 
      
 355 
     | 
    
         
            +
                    Adding extra logs before making a retry request     
         
     | 
| 
      
 356 
     | 
    
         
            +
                """
         
     | 
| 
      
 357 
     | 
    
         
            +
                def __init__(self, *args: Any, **kwargs: Any) -> Any:
         
     | 
| 
      
 358 
     | 
    
         
            +
                    self.thread_cancellation_token:Optional[threading.Event] = None
         
     | 
| 
      
 359 
     | 
    
         
            +
                    return super().__init__(*args, **kwargs)
         
     | 
| 
      
 360 
     | 
    
         
            +
                
         
     | 
| 
      
 361 
     | 
    
         
            +
                def new(self, **kw):
         
     | 
| 
      
 362 
     | 
    
         
            +
                    new_retry = super().new(**kw)
         
     | 
| 
      
 363 
     | 
    
         
            +
                    new_retry.thread_cancellation_token = self.thread_cancellation_token
         
     | 
| 
      
 364 
     | 
    
         
            +
                    return new_retry
         
     | 
| 
      
 365 
     | 
    
         
            +
                
         
     | 
| 
      
 366 
     | 
    
         
            +
                def sleep_for_retry(self, response=None):
         
     | 
| 
      
 367 
     | 
    
         
            +
                    retry_after = self.get_retry_after(response)
         
     | 
| 
      
 368 
     | 
    
         
            +
                    if retry_after:
         
     | 
| 
      
 369 
     | 
    
         
            +
                        logger.info(f"Retrying after {retry_after} seconds due to Retry-After header")
         
     | 
| 
      
 370 
     | 
    
         
            +
                        if self.thread_cancellation_token is None:
         
     | 
| 
      
 371 
     | 
    
         
            +
                            time.sleep(retry_after)
         
     | 
| 
      
 372 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 373 
     | 
    
         
            +
                            if self.thread_cancellation_token.wait(retry_after):
         
     | 
| 
      
 374 
     | 
    
         
            +
                                raise InterruptedWhileWaitingException(message="The sync was interrupted while waiting for rate limiting to expire")
         
     | 
| 
      
 375 
     | 
    
         
            +
                        return True
         
     | 
| 
      
 376 
     | 
    
         
            +
                    return False
         
     | 
| 
      
 377 
     | 
    
         
            +
             
     | 
| 
      
 378 
     | 
    
         
            +
                def _sleep_backoff(self):
         
     | 
| 
      
 379 
     | 
    
         
            +
                    backoff = self.get_backoff_time()
         
     | 
| 
      
 380 
     | 
    
         
            +
                    if backoff <= 0:
         
     | 
| 
      
 381 
     | 
    
         
            +
                        return
         
     | 
| 
      
 382 
     | 
    
         
            +
                    logger.info(f"Retrying after {backoff} seconds due to backoff time")
         
     | 
| 
      
 383 
     | 
    
         
            +
                    if self.thread_cancellation_token is None:
         
     | 
| 
      
 384 
     | 
    
         
            +
                        time.sleep(backoff)
         
     | 
| 
      
 385 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 386 
     | 
    
         
            +
                        if self.thread_cancellation_token.wait(backoff):
         
     | 
| 
      
 387 
     | 
    
         
            +
                            raise InterruptedWhileWaitingException(message="The sync was interrupted while waiting for rate limiting to expire")
         
     | 
| 
      
 388 
     | 
    
         
            +
             
     | 
| 
      
 389 
     | 
    
         
            +
             
     | 
| 
       352 
390 
     | 
    
         
             
            class RateLimitedSession(requests.Session):
         
     | 
| 
       353 
391 
     | 
    
         
             
                """
         
     | 
| 
       354 
392 
     | 
    
         
             
                Creates a requests session that will automatically handle rate limiting.
         
     | 
| 
         @@ -356,7 +394,13 @@ class RateLimitedSession(requests.Session): 
     | 
|
| 
       356 
394 
     | 
    
         
             
                The thread_cancellation_token is observed when waiting, as well as the overall run deadline.
         
     | 
| 
       357 
395 
     | 
    
         
             
                In case this is used across threads, the retry count will be tracked per request URL (minus query parameters). It will be cleared when the request is successful.
         
     | 
| 
       358 
396 
     | 
    
         
             
                """
         
     | 
| 
       359 
     | 
    
         
            -
                def __init__(self, 
     | 
| 
      
 397 
     | 
    
         
            +
                def __init__(self,
         
     | 
| 
      
 398 
     | 
    
         
            +
                             run_deadline:datetime.datetime,
         
     | 
| 
      
 399 
     | 
    
         
            +
                             thread_cancellation_token:threading.Event,
         
     | 
| 
      
 400 
     | 
    
         
            +
                             max_retries=5,
         
     | 
| 
      
 401 
     | 
    
         
            +
                             backoff_factor=1,
         
     | 
| 
      
 402 
     | 
    
         
            +
                             statuses_to_include:List[int] = [429],
         
     | 
| 
      
 403 
     | 
    
         
            +
                             respect_retry_after_header:bool = True):
         
     | 
| 
       360 
404 
     | 
    
         
             
                    super().__init__()
         
     | 
| 
       361 
405 
     | 
    
         
             
                    self.max_retries = max_retries
         
     | 
| 
       362 
406 
     | 
    
         
             
                    self.backoff_factor = backoff_factor
         
     | 
| 
         @@ -366,12 +410,14 @@ class RateLimitedSession(requests.Session): 
     | 
|
| 
       366 
410 
     | 
    
         
             
                    self.thread_cancellation_token = thread_cancellation_token
         
     | 
| 
       367 
411 
     | 
    
         
             
                    self.statuses_to_include = statuses_to_include
         
     | 
| 
       368 
412 
     | 
    
         | 
| 
       369 
     | 
    
         
            -
                    retry_strategy =  
     | 
| 
      
 413 
     | 
    
         
            +
                    retry_strategy = RetryWithLogging(
         
     | 
| 
       370 
414 
     | 
    
         
             
                        total=max_retries,
         
     | 
| 
       371 
415 
     | 
    
         
             
                        backoff_factor=backoff_factor,
         
     | 
| 
       372 
416 
     | 
    
         
             
                        status_forcelist=statuses_to_include,
         
     | 
| 
       373 
     | 
    
         
            -
                        allowed_methods=["HEAD", "GET", "OPTIONS", "POST", "PUT", "DELETE"]
         
     | 
| 
      
 417 
     | 
    
         
            +
                        allowed_methods=["HEAD", "GET", "OPTIONS", "POST", "PUT", "DELETE"],
         
     | 
| 
      
 418 
     | 
    
         
            +
                        respect_retry_after_header=respect_retry_after_header
         
     | 
| 
       374 
419 
     | 
    
         
             
                    )
         
     | 
| 
      
 420 
     | 
    
         
            +
                    retry_strategy.thread_cancellation_token = thread_cancellation_token
         
     | 
| 
       375 
421 
     | 
    
         
             
                    adapter = HTTPAdapter(max_retries=retry_strategy)
         
     | 
| 
       376 
422 
     | 
    
         
             
                    self.mount("https://", adapter)
         
     | 
| 
       377 
423 
     | 
    
         
             
                    self.mount("http://", adapter)
         
     | 
| 
         @@ -398,7 +444,7 @@ class RateLimitedSession(requests.Session): 
     | 
|
| 
       398 
444 
     | 
    
         
             
                def request(self, method, url, **kwargs):
         
     | 
| 
       399 
445 
     | 
    
         
             
                    while True:
         
     | 
| 
       400 
446 
     | 
    
         
             
                        response = super().request(method, url, **kwargs)
         
     | 
| 
       401 
     | 
    
         
            -
                        
         
     | 
| 
      
 447 
     | 
    
         
            +
                        # TODO: this is probably all redundant as the Retry object should handle this at a lower level (urllib3)
         
     | 
| 
       402 
448 
     | 
    
         
             
                        if response.status_code in self.statuses_to_include:
         
     | 
| 
       403 
449 
     | 
    
         
             
                            if 'Retry-After' in response.headers:
         
     | 
| 
       404 
450 
     | 
    
         
             
                                retry_after = response.headers['Retry-After']
         
     | 
| 
         @@ -3,10 +3,10 @@ omnata_plugin_runtime/api.py,sha256=W79CsAcl127Dzy-XVS9CzvzsbS3IigVH4QAhFFDkaXg, 
     | 
|
| 
       3 
3 
     | 
    
         
             
            omnata_plugin_runtime/configuration.py,sha256=7cMekoY8CeZAJHpASU6tCMidF55Hzfr7CD74jtebqIY,35742
         
     | 
| 
       4 
4 
     | 
    
         
             
            omnata_plugin_runtime/forms.py,sha256=pw_aKVsXSz47EP8PFBI3VDwdSN5IjvZxp8JTjO1V130,18421
         
     | 
| 
       5 
5 
     | 
    
         
             
            omnata_plugin_runtime/logging.py,sha256=bn7eKoNWvtuyTk7RTwBS9UARMtqkiICtgMtzq3KA2V0,3272
         
     | 
| 
       6 
     | 
    
         
            -
            omnata_plugin_runtime/omnata_plugin.py,sha256= 
     | 
| 
      
 6 
     | 
    
         
            +
            omnata_plugin_runtime/omnata_plugin.py,sha256=9ZXKoQTyRV_Yzz_e6wEw7rJtt8YDCNrGT0F0tg7Fecc,108571
         
     | 
| 
       7 
7 
     | 
    
         
             
            omnata_plugin_runtime/plugin_entrypoints.py,sha256=JAGEdVcy9QEXv7TO5zt7co64LTP8nqGusOc0sJG9GtU,29149
         
     | 
| 
       8 
     | 
    
         
            -
            omnata_plugin_runtime/rate_limiting.py,sha256= 
     | 
| 
       9 
     | 
    
         
            -
            omnata_plugin_runtime-0.4. 
     | 
| 
       10 
     | 
    
         
            -
            omnata_plugin_runtime-0.4. 
     | 
| 
       11 
     | 
    
         
            -
            omnata_plugin_runtime-0.4. 
     | 
| 
       12 
     | 
    
         
            -
            omnata_plugin_runtime-0.4. 
     | 
| 
      
 8 
     | 
    
         
            +
            omnata_plugin_runtime/rate_limiting.py,sha256=27_sgEkD7kmQlfSF3IaM09Hs8MA5tXuacVUOFR4zwC0,23454
         
     | 
| 
      
 9 
     | 
    
         
            +
            omnata_plugin_runtime-0.4.6.dist-info/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
         
     | 
| 
      
 10 
     | 
    
         
            +
            omnata_plugin_runtime-0.4.6.dist-info/METADATA,sha256=a9k4VwXCoEAfrLQ0KwqpLBJcfSR9t_LS2NO0Zg1Fskc,1638
         
     | 
| 
      
 11 
     | 
    
         
            +
            omnata_plugin_runtime-0.4.6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
         
     | 
| 
      
 12 
     | 
    
         
            +
            omnata_plugin_runtime-0.4.6.dist-info/RECORD,,
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     |