From eaf3a5472fa2fcd6dd0eed81847f29041ef80603 Mon Sep 17 00:00:00 2001 From: CoreyEWood Date: Fri, 6 Jun 2025 13:54:26 -0700 Subject: [PATCH 1/7] request timeout for submit_image_query --- src/groundlight/client.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/groundlight/client.py b/src/groundlight/client.py index a603edd3..9797d1b3 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -233,6 +233,10 @@ def _fixup_image_query(iq: ImageQuery) -> ImageQuery: iq.result.label = convert_internal_label_to_display(iq, iq.result.label) return iq + def _get_request_timeout(self, **kwargs): + """Extract request_timeout from kwargs or use default.""" + return kwargs.get("request_timeout", DEFAULT_REQUEST_TIMEOUT) + def whoami(self) -> str: """ Return the username (email address) associated with the current API token. @@ -637,6 +641,7 @@ def submit_image_query( # noqa: PLR0913 # pylint: disable=too-many-arguments, t inspection_id: Optional[str] = None, metadata: Union[dict, str, None] = None, image_query_id: Optional[str] = None, + **kwargs, ) -> ImageQuery: """ Evaluates an image with Groundlight. This is the core method for getting predictions about images. @@ -731,7 +736,11 @@ def submit_image_query( # noqa: PLR0913 # pylint: disable=too-many-arguments, t image_bytesio: ByteStreamWrapper = parse_supported_image_types(image) - params = {"detector_id": detector_id, "body": image_bytesio, "_request_timeout": DEFAULT_REQUEST_TIMEOUT} + params = { + "detector_id": detector_id, + "body": image_bytesio, + "_request_timeout": self._get_request_timeout(**kwargs), + } if patience_time is not None: params["patience_time"] = patience_time From 85032825835bc6b1f0fb4ee5e467bd236ddf8539 Mon Sep 17 00:00:00 2001 From: CoreyEWood Date: Fri, 6 Jun 2025 14:29:43 -0700 Subject: [PATCH 2/7] add test --- test/integration/test_groundlight.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index c68e47ea..237a22f4 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -84,9 +84,9 @@ def test_create_detector(gl: Groundlight): _detector = gl.create_detector(name=name, query=query) assert str(_detector) assert isinstance(_detector, Detector) - assert ( - _detector.confidence_threshold == DEFAULT_CONFIDENCE_THRESHOLD - ), "We expected the default confidence threshold to be used." + assert _detector.confidence_threshold == DEFAULT_CONFIDENCE_THRESHOLD, ( + "We expected the default confidence threshold to be used." + ) # Test creating dectors with other modes name = f"Test {datetime.utcnow()}" # Need a unique name @@ -160,9 +160,9 @@ def test_create_detector_with_confidence_threshold(gl: Groundlight): # If the confidence is not provided, we will use the existing detector's confidence. retrieved_detector = gl.get_or_create_detector(name=name, query=query) - assert ( - retrieved_detector.confidence_threshold == confidence_threshold - ), "We expected to retrieve the existing detector's confidence, but got a different value." + assert retrieved_detector.confidence_threshold == confidence_threshold, ( + "We expected to retrieve the existing detector's confidence, but got a different value." + ) @pytest.mark.skip_for_edge_endpoint(reason="The edge-endpoint does not support passing detector metadata.") @@ -348,6 +348,11 @@ def test_submit_image_query_with_human_review_param(gl: Groundlight, detector: D assert is_valid_display_result(_image_query.result) +def test_submit_image_query_with_request_timeout(gl: Groundlight, detector: Detector, image: str): + """Verifies that request_timeout can be passed to submit_image_query.""" + gl.submit_image_query(detector=detector, image=image, human_review="NEVER", request_timeout=5) + + @pytest.mark.skip_for_edge_endpoint(reason="The edge-endpoint does not support passing detector metadata.") def test_create_detector_with_metadata(gl: Groundlight): name = f"Test {datetime.utcnow()}" # Need a unique name From 2fc218280d7a026d8d41b56fdfc9109ba8135554 Mon Sep 17 00:00:00 2001 From: Auto-format Bot Date: Fri, 6 Jun 2025 21:30:24 +0000 Subject: [PATCH 3/7] Automatically reformatting code --- test/integration/test_groundlight.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 237a22f4..2dd57afc 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -84,9 +84,9 @@ def test_create_detector(gl: Groundlight): _detector = gl.create_detector(name=name, query=query) assert str(_detector) assert isinstance(_detector, Detector) - assert _detector.confidence_threshold == DEFAULT_CONFIDENCE_THRESHOLD, ( - "We expected the default confidence threshold to be used." - ) + assert ( + _detector.confidence_threshold == DEFAULT_CONFIDENCE_THRESHOLD + ), "We expected the default confidence threshold to be used." # Test creating dectors with other modes name = f"Test {datetime.utcnow()}" # Need a unique name @@ -160,9 +160,9 @@ def test_create_detector_with_confidence_threshold(gl: Groundlight): # If the confidence is not provided, we will use the existing detector's confidence. retrieved_detector = gl.get_or_create_detector(name=name, query=query) - assert retrieved_detector.confidence_threshold == confidence_threshold, ( - "We expected to retrieve the existing detector's confidence, but got a different value." - ) + assert ( + retrieved_detector.confidence_threshold == confidence_threshold + ), "We expected to retrieve the existing detector's confidence, but got a different value." @pytest.mark.skip_for_edge_endpoint(reason="The edge-endpoint does not support passing detector metadata.") From b7455f39b23bd2d331b28b109b9bd319e18a263a Mon Sep 17 00:00:00 2001 From: CoreyEWood Date: Mon, 9 Jun 2025 14:09:27 -0700 Subject: [PATCH 4/7] "Someday, I'm gonna be a real parameter!" --- src/groundlight/client.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/groundlight/client.py b/src/groundlight/client.py index 9797d1b3..09308439 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -233,10 +233,6 @@ def _fixup_image_query(iq: ImageQuery) -> ImageQuery: iq.result.label = convert_internal_label_to_display(iq, iq.result.label) return iq - def _get_request_timeout(self, **kwargs): - """Extract request_timeout from kwargs or use default.""" - return kwargs.get("request_timeout", DEFAULT_REQUEST_TIMEOUT) - def whoami(self) -> str: """ Return the username (email address) associated with the current API token. @@ -641,7 +637,7 @@ def submit_image_query( # noqa: PLR0913 # pylint: disable=too-many-arguments, t inspection_id: Optional[str] = None, metadata: Union[dict, str, None] = None, image_query_id: Optional[str] = None, - **kwargs, + request_timeout: Optional[float] = None, ) -> ImageQuery: """ Evaluates an image with Groundlight. This is the core method for getting predictions about images. @@ -723,6 +719,8 @@ def submit_image_query( # noqa: PLR0913 # pylint: disable=too-many-arguments, t :param image_query_id: The ID for the image query. This is to enable specific functionality and is not intended for general external use. If not set, a random ID will be generated. + :param request_timeout: The total request timeout for the image query submission API request. Most users will + not need to modify this. If not set, the default value will be used. :return: ImageQuery with query details and result (if wait > 0) :raises ValueError: If wait > 0 when want_async=True @@ -739,7 +737,7 @@ def submit_image_query( # noqa: PLR0913 # pylint: disable=too-many-arguments, t params = { "detector_id": detector_id, "body": image_bytesio, - "_request_timeout": self._get_request_timeout(**kwargs), + "_request_timeout": request_timeout if request_timeout is not None else DEFAULT_REQUEST_TIMEOUT, } if patience_time is not None: From e566bbb2be9c622a37013e3ac8c2ba5dfcc6fd65 Mon Sep 17 00:00:00 2001 From: CoreyEWood Date: Wed, 11 Jun 2025 15:50:36 -0700 Subject: [PATCH 5/7] improve test to actually test the behavior --- test/integration/test_groundlight.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 2dd57afc..61b28938 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -7,6 +7,7 @@ import time from datetime import datetime from typing import Any, Dict, Optional, Union +from urllib3.exceptions import ReadTimeoutError import pytest from groundlight import Groundlight @@ -348,9 +349,15 @@ def test_submit_image_query_with_human_review_param(gl: Groundlight, detector: D assert is_valid_display_result(_image_query.result) -def test_submit_image_query_with_request_timeout(gl: Groundlight, detector: Detector, image: str): - """Verifies that request_timeout can be passed to submit_image_query.""" - gl.submit_image_query(detector=detector, image=image, human_review="NEVER", request_timeout=5) +def test_submit_image_query_with_low_request_timeout(gl: Groundlight, detector: Detector, image: str): + """ + Test that submit_image_query respects the request_timeout parameter and raises ReadTimeoutError when timeout is + exceeded. + """ + with pytest.raises(ReadTimeoutError): + # Setting a very low request_timeout value should result in a timeout. + # NOTE: request_timeout=0 seems to have special behavior that does not result in a timeout. + gl.submit_image_query(detector=detector, image=image, human_review="NEVER", request_timeout=1e-8) @pytest.mark.skip_for_edge_endpoint(reason="The edge-endpoint does not support passing detector metadata.") From 9bb3e439900a91680d3158c1feb523932bfcd321 Mon Sep 17 00:00:00 2001 From: Auto-format Bot Date: Wed, 11 Jun 2025 22:51:16 +0000 Subject: [PATCH 6/7] Automatically reformatting code --- test/integration/test_groundlight.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 61b28938..9b93bbdc 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -7,7 +7,6 @@ import time from datetime import datetime from typing import Any, Dict, Optional, Union -from urllib3.exceptions import ReadTimeoutError import pytest from groundlight import Groundlight @@ -27,6 +26,7 @@ PaginatedDetectorList, PaginatedImageQueryList, ) +from urllib3.exceptions import ReadTimeoutError DEFAULT_CONFIDENCE_THRESHOLD = 0.9 IQ_IMPROVEMENT_THRESHOLD = 0.75 From c8506f19c735b672752ee5be17cb0a11c168c90e Mon Sep 17 00:00:00 2001 From: CoreyEWood Date: Wed, 11 Jun 2025 15:53:02 -0700 Subject: [PATCH 7/7] very important change --- test/integration/test_groundlight.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 9b93bbdc..40c74f12 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -351,7 +351,7 @@ def test_submit_image_query_with_human_review_param(gl: Groundlight, detector: D def test_submit_image_query_with_low_request_timeout(gl: Groundlight, detector: Detector, image: str): """ - Test that submit_image_query respects the request_timeout parameter and raises ReadTimeoutError when timeout is + Test that submit_image_query respects the request_timeout parameter and raises a ReadTimeoutError when timeout is exceeded. """ with pytest.raises(ReadTimeoutError):