From 7aca51f06334344880d1f6688f24b899f127c0b3 Mon Sep 17 00:00:00 2001 From: Bruno Postle Date: Fri, 10 Apr 2026 00:03:07 +0100 Subject: [PATCH] bsdd: retry on HTTP 429 rate-limit responses in Client.get() The bSDD API rate-limits aggressively; without retry logic the client silently returns the 429 JSON dict, causing KeyError when callers access expected response keys. Client.get() now retries up to 5 times, sleeping for the Retry-After header value (defaulting to 5s). Remove the manual time.sleep() calls from the test module now that the client handles rate limiting automatically. Generated with the assistance of an AI coding tool. --- src/bsdd/bsdd.py | 8 +++++++- src/bsdd/tests/test_bsdd.py | 9 +-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/bsdd/bsdd.py b/src/bsdd/bsdd.py index fbc14cc393..d0e30cbfea 100644 --- a/src/bsdd/bsdd.py +++ b/src/bsdd/bsdd.py @@ -521,7 +521,13 @@ class Client: headers = {"User-Agent": "IfcOpenShell.bSDD.py/0.8.0"} if is_auth_required: headers["Authorization"] = "Bearer " + self.get_access_token() - return requests.get(f"{self.baseurl}{endpoint}", timeout=10, headers=headers, params=params or None).json() + for _ in range(5): + response = requests.get(f"{self.baseurl}{endpoint}", timeout=10, headers=headers, params=params or None) + if response.status_code != 429: + return response.json() + retry_after = int(response.headers.get("Retry-After", 5)) + time.sleep(retry_after) + return response.json() def _get_deprecated(self, endpoint, params=None, is_auth_required=False): headers = {"User-Agent": "IfcOpenShell.bSDD.py/0.8.0"} diff --git a/src/bsdd/tests/test_bsdd.py b/src/bsdd/tests/test_bsdd.py index 7c62a1d788..7d975a2da0 100644 --- a/src/bsdd/tests/test_bsdd.py +++ b/src/bsdd/tests/test_bsdd.py @@ -1,26 +1,19 @@ -import time - from bsdd import Client client = Client() # Fetch shared data at module level to avoid repeated API calls during tests. -# Sleeps are required: the bSDD API rate-limits to roughly one request per second. +# The Client.get() method handles 429 rate-limit responses with automatic retry. _dictionaries = client.get_dictionary()["dictionaries"] ifc4x3_uri = next(l["uri"] for l in _dictionaries if "4.3" in l["uri"]) nbs_uri = next(l["uri"] for l in _dictionaries if "Uniclass 2015" == l["name"]) -time.sleep(2) _ifc4x3_classes = client.get_classes(ifc4x3_uri, use_nested_classes=False, class_type="Class") -time.sleep(2) _nbs_classes = client.get_classes(nbs_uri, use_nested_classes=False, class_type="Class", offset=0, limit=5) _uri_light_fixture = next(l for l in _ifc4x3_classes["classes"] if "IfcLightFixture" == l["code"])["uri"] -time.sleep(2) _light_fixture = client.get_class(_uri_light_fixture) -time.sleep(2) _light_fixture_relations = client.get_class_relations(_uri_light_fixture) -time.sleep(2) _light_fixture_properties = client.get_class_properties(_uri_light_fixture)