Pyzotero: a Python client for the Zotero API pyzotero.readthedocs.io
zotero

Simplify backoff: remove threading, use timestamp only


Signed-off-by: Stephan Hügel <shugel@tcd.ie>

+14 -33
+8 -29
src/pyzotero/_client.py
··· 9 9 import copy 10 10 import json 11 11 import re 12 - import threading 13 12 import time 14 13 from pathlib import Path, PurePosixPath 15 14 from urllib.parse import ( ··· 156 155 self.self_link = {} 157 156 self.templates = {} 158 157 self.savedsearch = None 159 - # these are required for backoff handling 160 - self.backoff = False 161 - self.backoff_duration = 0.0 158 + # backoff handling: timestamp when backoff expires (0.0 = no backoff) 159 + self.backoff_until = 0.0 162 160 163 161 def __del__(self): 164 162 """Remove client before cleanup.""" ··· 186 184 return url 187 185 188 186 def _set_backoff(self, duration): 189 - """Set a backoff. 190 - 191 - Spins up a timer in a background thread which resets the backoff logic 192 - when it expires, then sets the time at which the backoff will expire. 193 - The latter step is required so that other calls can check whether there's 194 - an active backoff, because the threading.Timer method has no way 195 - of returning a duration. 196 - """ 197 - duration = float(duration) 198 - self.backoff = True 199 - threading.Timer(duration, self._reset_backoff).start() 200 - self.backoff_duration = time.time() + duration 201 - 202 - def _reset_backoff(self): 203 - self.backoff = False 204 - self.backoff_duration = 0.0 187 + """Set backoff expiration time.""" 188 + self.backoff_until = time.time() + float(duration) 205 189 206 190 def _check_backoff(self): 207 - """Before an API call is made, check whether there's an active backoff. 208 - 209 - If there is, check whether there's any time left on the backoff. 210 - If there is, sleep for the remainder before returning. 211 - """ 212 - if self.backoff: 213 - remainder = self.backoff_duration - time.time() 214 - if remainder > 0.0: 215 - time.sleep(remainder) 191 + """Wait if backoff is active.""" 192 + remainder = self.backoff_until - time.time() 193 + if remainder > 0.0: 194 + time.sleep(remainder) 216 195 217 196 def default_headers(self): 218 197 """Return headers that are always OK to include."""
+6 -4
tests/test_zotero.py
··· 195 195 adding_headers={"backoff": 0.2}, 196 196 ) 197 197 zot.items() 198 - self.assertTrue(zot.backoff) 198 + # backoff_until should be in the future 199 + self.assertGreater(zot.backoff_until, time.time()) 199 200 time.sleep(0.3) 200 - # Timer will have expired, triggering backoff reset 201 - self.assertFalse(zot.backoff) 201 + # backoff_until should now be in the past 202 + self.assertLess(zot.backoff_until, time.time()) 202 203 203 204 @httpretty.activate 204 205 def testGetItemFile(self): ··· 790 791 adding_headers={"backoff": 0.1}, 791 792 ) 792 793 zot.items() 793 - self.assertTrue(zot.backoff) 794 + # backoff_until should be in the future 795 + self.assertGreater(zot.backoff_until, time.time()) 794 796 795 797 @httpretty.activate 796 798 def testDeleteTags(self):