From bd6c263eb96541fe27fa772bcd8e17e2bbdee6bc Mon Sep 17 00:00:00 2001 From: Andrea Orlandi Date: Tue, 14 Jul 2026 10:38:05 +0200 Subject: [PATCH] [base] Reduce likeliness of being throttled when uploading blobs --- src/picterra/base_client.py | 21 +++++++++++++++++++++ tests/conftest.py | 25 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 tests/conftest.py diff --git a/src/picterra/base_client.py b/src/picterra/base_client.py index cf0df07..233115d 100644 --- a/src/picterra/base_client.py +++ b/src/picterra/base_client.py @@ -95,8 +95,28 @@ def _download_to_file(url: str, filename: str): def _upload_file_to_blobstore(upload_url: str, filename: str): + """Uploads a file to a blobstore URL + + Note that since the common upload pattern is: + 1. get upload URL, POST + 2. upload file to URL (this function), PUT + 3. commit upload, POST + 4. wait for operation to complete, GET + and we retry if throttled only on GETs, we add two pauses before and after this + function so that we avoid incurring in the rate limit in the POST-PUT-POST sequence. + + Args: + upload_url: The pre-signed URL to upload the file to. + filename: The local path to the file to upload. + + Raises: + ValueError: If the provided filename does not exist or is not a file. + APIError: If the upload to the blobstore fails. + + """ if not (os.path.exists(filename) and os.path.isfile(filename)): raise ValueError("Invalid file: " + filename) + time.sleep(2) with open( filename, "rb" ) as f: # binary recommended by requests stream upload (see link below) @@ -109,6 +129,7 @@ def _upload_file_to_blobstore(upload_url: str, filename: str): if not resp.ok: logger.error("Error when uploading to blobstore %s" % upload_url) raise APIError(resp.text) + time.sleep(2) def multipolygon_to_polygon_feature_collection(mp): diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..0be12d8 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,25 @@ +import asyncio +import time + +import pytest + + +@pytest.fixture(autouse=True) +def fast_sleep(monkeypatch): + """Replace real sleeps with fast no-ops to keep tests quick and deterministic. + + This fixture is autouse so it applies to all tests. If a specific test + requires real sleeping behavior, it can explicitly restore the original + functions. + """ + + # Patch time.sleep to a no-op + monkeypatch.setattr(time, "sleep", lambda s: None) + + # Patch asyncio.sleep to an async no-op + async def _async_noop(_): + return None + + monkeypatch.setattr(asyncio, "sleep", _async_noop) + + yield