omnata-plugin-runtime 0.4.5__py3-none-any.whl → 0.4.6__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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, run_deadline:datetime.datetime, thread_cancellation_token:threading.Event, max_retries=5, backoff_factor=1, statuses_to_include:List[int] = [429]):
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 = Retry(
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']
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omnata-plugin-runtime
3
- Version: 0.4.5
3
+ Version: 0.4.6
4
4
  Summary: Classes and common runtime components for building and running Omnata Plugins
5
5
  Author: James Weakley
6
6
  Author-email: james.weakley@omnata.com
@@ -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=UJZNTcG6s5APZ7EIOYc5N4bFiyFEq3ekbSP6EK7tCBY,108065
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=29Hjsr0i1rE8jERVdIFGINfQfp_kI3PDft-IM_ZxvCA,21509
9
- omnata_plugin_runtime-0.4.5.dist-info/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
10
- omnata_plugin_runtime-0.4.5.dist-info/METADATA,sha256=0d4xQ6hbq-twZnIkK7UyU_lp1h6lCwFUqldIAyVZDHI,1638
11
- omnata_plugin_runtime-0.4.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
12
- omnata_plugin_runtime-0.4.5.dist-info/RECORD,,
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,,