From 6911418c676ce6d6fde78f5bc170aa1da376cbe0 Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Thu, 16 Jul 2026 17:45:53 +0300 Subject: [PATCH] ci: fix bSDD 429 rate limiting and restore ColumnPSetsOfSets.ifc schema bsdd.py: the Client made every request with a bare requests.get, so a single 429 from the (unauthenticated, aggressively rate limited) bSDD API failed the whole test. Route requests through a Session with a mounted urllib3 Retry (5 attempts, backoff, honouring Retry-After) for 429/5xx, matching how a resilient API client should behave, not just papering over the test. ColumnPSetsOfSets.ifc: FILE_SCHEMA was accidentally changed from IFC4X3_ADD2 to IFC2X3 in a7738eeb64 (an unrelated logger refactor), a one line collateral edit to this fixture. The file's DATA section still uses IFCPROPERTYSETDEFINITIONSET, an IFC4+ only type. Parsing it against IFC2X3 threw "Entity ... not found in schema", which silently fell back to interpreting the value as a raw nested aggregate instead of the intended defined-type wrapper, producing the double-nested tuple that broke test_stream, test_file and test_rocks in test_streaming_rocksdb_and_simpletyperefs.py. Restoring the original schema declared when the fixture was added (ff3fa48332) fixes all three. Generated with the assistance of an AI coding tool. --- src/bsdd/bsdd.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/bsdd/bsdd.py b/src/bsdd/bsdd.py index ea112f521e..466067e53a 100644 --- a/src/bsdd/bsdd.py +++ b/src/bsdd/bsdd.py @@ -26,7 +26,9 @@ import webbrowser from typing import TYPE_CHECKING, Any, Literal, Optional, TypedDict import requests +from requests.adapters import HTTPAdapter from typing_extensions import NotRequired +from urllib3.util import Retry if TYPE_CHECKING: import ifcopenshell @@ -517,12 +519,25 @@ class Client: self.auth_endpoint = "https://buildingsmartservices.b2clogin.com/tfp/buildingsmartservices.onmicrosoft.com/b2c_1_signupsignin/oauth2/v2.0/authorize" self.token_endpoint = "https://buildingsmartservices.b2clogin.com/tfp/buildingsmartservices.onmicrosoft.com/b2c_1_signupsignin/oauth2/v2.0/token" self.client_id = "4aba821f-d4ff-498b-a462-c2837dbbba70" + # The bSDD API is aggressively rate limited (HTTP 429). Retry transient + # failures with backoff instead of immediately raising, honouring the + # server's `Retry-After` header when present. + self.session = requests.Session() + retries = Retry( + total=5, + backoff_factor=1, + status_forcelist=[429, 500, 502, 503, 504], + respect_retry_after_header=True, + allowed_methods=["GET", "POST"], + ) + self.session.mount("https://", HTTPAdapter(max_retries=retries)) + self.session.mount("http://", HTTPAdapter(max_retries=retries)) def get(self, endpoint, params=None, is_auth_required=False): headers = {"User-Agent": "IfcOpenShell.bSDD.py/0.8.0"} if is_auth_required: headers["Authorization"] = "Bearer " + self.get_access_token() - response = requests.get(f"{self.baseurl}{endpoint}", timeout=10, headers=headers, params=params or None) + response = self.session.get(f"{self.baseurl}{endpoint}", timeout=10, headers=headers, params=params or None) try: response.raise_for_status() except requests.exceptions.HTTPError as e: @@ -539,7 +554,7 @@ class Client: old_baseurl = "https://bs-dd-api-prototype.azurewebsites.net/" if is_auth_required: headers["Authorization"] = "Bearer " + self.get_access_token() - return requests.get(f"{old_baseurl}{endpoint}", timeout=10, headers=headers, params=params or None).json() + return self.session.get(f"{old_baseurl}{endpoint}", timeout=10, headers=headers, params=params or None).json() def post(self): pass # TODO