From 3b91100baea117c04a1d6d07f7b037dd74fc701c Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 10 Mar 2026 18:59:28 +0000 Subject: [PATCH 1/4] add detector priming group --- .../docs/DetectorCreationInputRequest.md | 1 + generated/docs/DetectorsApi.md | 1 + .../model/detector_creation_input_request.py | 11 ++ generated/model.py | 5 +- package-lock.json | 6 +- spec/public-api.yaml | 7 + src/groundlight/client.py | 16 ++ test/integration/test_groundlight.py | 162 +++++++++++++++++ test/unit/test_priming_group.py | 171 ++++++++++++++++++ 9 files changed, 378 insertions(+), 2 deletions(-) create mode 100644 test/unit/test_priming_group.py diff --git a/generated/docs/DetectorCreationInputRequest.md b/generated/docs/DetectorCreationInputRequest.md index 16adba94..630aca57 100644 --- a/generated/docs/DetectorCreationInputRequest.md +++ b/generated/docs/DetectorCreationInputRequest.md @@ -14,6 +14,7 @@ Name | Type | Description | Notes **metadata** | **str** | Base64-encoded metadata for the detector. This should be a JSON object with string keys. The size after encoding should not exceed 1362 bytes, corresponding to 1KiB before encoding. | [optional] **mode** | **bool, date, datetime, dict, float, int, list, str, none_type** | Mode in which this detector will work. * `BINARY` - BINARY * `COUNT` - COUNT * `MULTI_CLASS` - MULTI_CLASS * `TEXT` - TEXT * `BOUNDING_BOX` - BOUNDING_BOX | [optional] **mode_configuration** | **bool, date, datetime, dict, float, int, list, str, none_type** | | [optional] +**priming_group_id** | **str, none_type** | ID of an existing PrimingGroup to associate with this detector (optional). | [optional] **any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/generated/docs/DetectorsApi.md b/generated/docs/DetectorsApi.md index 9f5355e5..acd3761b 100644 --- a/generated/docs/DetectorsApi.md +++ b/generated/docs/DetectorsApi.md @@ -62,6 +62,7 @@ with groundlight_openapi_client.ApiClient(configuration) as api_client: metadata="metadata_example", mode=None, mode_configuration=None, + priming_group_id="priming_group_id_example", ) # DetectorCreationInputRequest | # example passing only required values which don't have defaults set diff --git a/generated/groundlight_openapi_client/model/detector_creation_input_request.py b/generated/groundlight_openapi_client/model/detector_creation_input_request.py index ca1b1563..ce1cbcec 100644 --- a/generated/groundlight_openapi_client/model/detector_creation_input_request.py +++ b/generated/groundlight_openapi_client/model/detector_creation_input_request.py @@ -97,6 +97,10 @@ class DetectorCreationInputRequest(ModelNormal): "max_length": 1362, "min_length": 1, }, + ("priming_group_id",): { + "max_length": 44, + "min_length": 1, + }, } @cached_property @@ -164,6 +168,10 @@ def openapi_types(): str, none_type, ), # noqa: E501 + "priming_group_id": ( + str, + none_type, + ), # noqa: E501 } @cached_property @@ -180,6 +188,7 @@ def discriminator(): "metadata": "metadata", # noqa: E501 "mode": "mode", # noqa: E501 "mode_configuration": "mode_configuration", # noqa: E501 + "priming_group_id": "priming_group_id", # noqa: E501 } read_only_vars = {} @@ -233,6 +242,7 @@ def _from_openapi_data(cls, name, query, *args, **kwargs): # noqa: E501 metadata (str): Base64-encoded metadata for the detector. This should be a JSON object with string keys. The size after encoding should not exceed 1362 bytes, corresponding to 1KiB before encoding.. [optional] # noqa: E501 mode (bool, date, datetime, dict, float, int, list, str, none_type): Mode in which this detector will work. * `BINARY` - BINARY * `COUNT` - COUNT * `MULTI_CLASS` - MULTI_CLASS * `TEXT` - TEXT * `BOUNDING_BOX` - BOUNDING_BOX. [optional] # noqa: E501 mode_configuration (bool, date, datetime, dict, float, int, list, str, none_type): [optional] # noqa: E501 + priming_group_id (str, none_type): ID of an existing PrimingGroup to associate with this detector (optional).. [optional] # noqa: E501 """ _check_type = kwargs.pop("_check_type", True) @@ -330,6 +340,7 @@ def __init__(self, name, query, *args, **kwargs): # noqa: E501 metadata (str): Base64-encoded metadata for the detector. This should be a JSON object with string keys. The size after encoding should not exceed 1362 bytes, corresponding to 1KiB before encoding.. [optional] # noqa: E501 mode (bool, date, datetime, dict, float, int, list, str, none_type): Mode in which this detector will work. * `BINARY` - BINARY * `COUNT` - COUNT * `MULTI_CLASS` - MULTI_CLASS * `TEXT` - TEXT * `BOUNDING_BOX` - BOUNDING_BOX. [optional] # noqa: E501 mode_configuration (bool, date, datetime, dict, float, int, list, str, none_type): [optional] # noqa: E501 + priming_group_id (str, none_type): ID of an existing PrimingGroup to associate with this detector (optional).. [optional] # noqa: E501 """ _check_type = kwargs.pop("_check_type", True) diff --git a/generated/model.py b/generated/model.py index 7b3636f5..726a0548 100644 --- a/generated/model.py +++ b/generated/model.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: public-api.yaml -# timestamp: 2026-01-13T18:34:24+00:00 +# timestamp: 2026-03-10T18:06:07+00:00 from __future__ import annotations @@ -429,6 +429,9 @@ class DetectorCreationInputRequest(BaseModel): mode_configuration: Optional[ Union[CountModeConfiguration, MultiClassModeConfiguration, TextModeConfiguration, BoundingBoxModeConfiguration] ] = None + priming_group_id: Optional[constr(min_length=1, max_length=44)] = Field( + None, description="ID of an existing PrimingGroup to associate with this detector (optional)." + ) class ImageQuery(BaseModel): diff --git a/package-lock.json b/package-lock.json index 7db9430a..51c40fa1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -184,6 +184,7 @@ "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.6.tgz", "integrity": "sha512-krKwLLcFmeuKDqngG2N/RuZHCs2ycsKcxWIDgcm7i1lf3sQ0iG03ci+DsP/r3FcT/eJDFsIHnKtNta2LIi7PzQ==", "license": "MIT", + "peer": true, "dependencies": { "file-type": "21.0.0", "iterare": "1.2.1", @@ -473,6 +474,7 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", "license": "MIT", + "peer": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", @@ -2569,7 +2571,8 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/rehype-katex": { "version": "7.0.1", @@ -2648,6 +2651,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.1.0" } diff --git a/spec/public-api.yaml b/spec/public-api.yaml index bb58a6ef..c60e03b0 100644 --- a/spec/public-api.yaml +++ b/spec/public-api.yaml @@ -1084,6 +1084,13 @@ components: - $ref: '#/components/schemas/TextModeConfiguration' - $ref: '#/components/schemas/BoundingBoxModeConfiguration' nullable: true + priming_group_id: + type: string + nullable: true + minLength: 1 + description: ID of an existing PrimingGroup to associate with this detector + (optional). + maxLength: 44 required: - name - query diff --git a/src/groundlight/client.py b/src/groundlight/client.py index 669d3a7c..c4609bf5 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -352,6 +352,7 @@ def _prep_create_detector( # noqa: PLR0913 # pylint: disable=too-many-arguments patience_time: Optional[float] = None, pipeline_config: Optional[str] = None, metadata: Union[dict, str, None] = None, + priming_group_id: Optional[str] = None, ) -> Detector: """ A helper function to prepare the input for creating a detector. Individual create_detector @@ -372,6 +373,8 @@ def _prep_create_detector( # noqa: PLR0913 # pylint: disable=too-many-arguments patience_time = float(patience_time) if patience_time: detector_creation_input.patience_time = patience_time + if priming_group_id is not None: + detector_creation_input.priming_group_id = priming_group_id return detector_creation_input def create_detector( # noqa: PLR0913 @@ -386,6 +389,7 @@ def create_detector( # noqa: PLR0913 pipeline_config: Optional[str] = None, metadata: Union[dict, str, None] = None, class_names: Optional[Union[List[str], str]] = None, + priming_group_id: Optional[str] = None, ) -> Detector: """ Create a new Detector with a given name and query. @@ -443,6 +447,7 @@ def create_detector( # noqa: PLR0913 metadata later by calling `get_detector()`. :param class_names: The name or names of the class to use for the detector. Only used for multi-class and counting detectors. + :param priming_group_id: Optional ID of an existing PrimingGroup to associate with this detector. :return: The created Detector object """ @@ -458,6 +463,7 @@ def create_detector( # noqa: PLR0913 patience_time=patience_time, pipeline_config=pipeline_config, metadata=metadata, + priming_group_id=priming_group_id, ) if mode == ModeEnum.COUNT: if class_names is None: @@ -473,6 +479,7 @@ def create_detector( # noqa: PLR0913 patience_time=patience_time, pipeline_config=pipeline_config, metadata=metadata, + priming_group_id=priming_group_id, ) if mode == ModeEnum.MULTI_CLASS: if class_names is None: @@ -488,6 +495,7 @@ def create_detector( # noqa: PLR0913 patience_time=patience_time, pipeline_config=pipeline_config, metadata=metadata, + priming_group_id=priming_group_id, ) raise ValueError( f"Unsupported mode: {mode}, check if your desired mode is only supported in the ExperimentalApi" @@ -1557,6 +1565,7 @@ def create_counting_detector( # noqa: PLR0913 # pylint: disable=too-many-argume patience_time: Optional[float] = None, pipeline_config: Optional[str] = None, metadata: Union[dict, str, None] = None, + priming_group_id: Optional[str] = None, ) -> Detector: """ Creates a counting detector that can count objects in images up to a specified maximum count. @@ -1595,6 +1604,7 @@ def create_counting_detector( # noqa: PLR0913 # pylint: disable=too-many-argume the detector (limited to 1KB). This metadata can be used to store additional information like location, purpose, or related system IDs. You can retrieve this metadata later by calling `get_detector()`. + :param priming_group_id: Optional ID of an existing PrimingGroup to associate with this detector. :return: The created Detector object """ @@ -1607,6 +1617,7 @@ def create_counting_detector( # noqa: PLR0913 # pylint: disable=too-many-argume patience_time=patience_time, pipeline_config=pipeline_config, metadata=metadata, + priming_group_id=priming_group_id, ) detector_creation_input.mode = ModeEnum.COUNT @@ -1629,6 +1640,7 @@ def create_binary_detector( # noqa: PLR0913 # pylint: disable=too-many-argument patience_time: Optional[float] = None, pipeline_config: Optional[str] = None, metadata: Union[dict, str, None] = None, + priming_group_id: Optional[str] = None, ) -> Detector: """ Creates a binary detector with the given name and query. @@ -1656,6 +1668,7 @@ def create_binary_detector( # noqa: PLR0913 # pylint: disable=too-many-argument patience_time=patience_time, pipeline_config=pipeline_config, metadata=metadata, + priming_group_id=priming_group_id, ) obj = self.detectors_api.create_detector(detector_creation_input, _request_timeout=DEFAULT_REQUEST_TIMEOUT) return Detector.parse_obj(obj.to_dict()) @@ -1671,6 +1684,7 @@ def create_multiclass_detector( # noqa: PLR0913 # pylint: disable=too-many-argu patience_time: Optional[float] = None, pipeline_config: Optional[str] = None, metadata: Union[dict, str, None] = None, + priming_group_id: Optional[str] = None, ) -> Detector: """ Creates a multiclass detector with the given name and query. @@ -1705,6 +1719,7 @@ def create_multiclass_detector( # noqa: PLR0913 # pylint: disable=too-many-argu the detector (limited to 1KB). This metadata can be used to store additional information like location, purpose, or related system IDs. You can retrieve this metadata later by calling `get_detector()`. + :param priming_group_id: Optional ID of an existing PrimingGroup to associate with this detector. :return: The created Detector object """ @@ -1717,6 +1732,7 @@ def create_multiclass_detector( # noqa: PLR0913 # pylint: disable=too-many-argu patience_time=patience_time, pipeline_config=pipeline_config, metadata=metadata, + priming_group_id=priming_group_id, ) detector_creation_input.mode = ModeEnum.MULTI_CLASS mode_config = MultiClassModeConfiguration(class_names=class_names) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 902774eb..704f296c 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -937,3 +937,165 @@ def test_delete_detector(gl: Groundlight): fake_detector_id = "det_fake123456789" with pytest.raises(NotFoundError): gl.delete_detector(fake_detector_id) # type: ignore + + +def test_create_detector_with_priming_group_id(gl: Groundlight): + """ + Test creating a detector with a priming_group_id parameter. + """ + name = f"Test priming group {datetime.utcnow()}" + query = "Is there a dog?" + pipeline_config = "never-review" + # Using a sample priming group ID format + priming_group_id = "prgrp_test123456789012345678901234567890" + + detector = gl.create_detector( + name=name, + query=query, + pipeline_config=pipeline_config, + priming_group_id=priming_group_id, + ) + assert str(detector) + assert isinstance(detector, Detector) + # Note: priming_group_id might not be returned in the detector object + # depending on the API implementation + + +def test_create_binary_detector_with_priming_group_id(gl: Groundlight): + """ + Test creating a binary detector with a priming_group_id parameter. + """ + name = f"Test binary priming {datetime.utcnow()}" + query = "Is there a cat?" + pipeline_config = "never-review" + priming_group_id = "prgrp_test123456789012345678901234567890" + + detector = gl.create_binary_detector( + name=name, + query=query, + pipeline_config=pipeline_config, + priming_group_id=priming_group_id, + ) + assert str(detector) + assert isinstance(detector, Detector) + + +def test_create_counting_detector_with_priming_group_id(gl: Groundlight): + """ + Test creating a counting detector with a priming_group_id parameter. + """ + name = f"Test counting priming {datetime.utcnow()}" + query = "How many dogs are present?" + class_name = "dog" + pipeline_config = "never-review" + priming_group_id = "prgrp_test123456789012345678901234567890" + + detector = gl.create_counting_detector( + name=name, + query=query, + class_name=class_name, + pipeline_config=pipeline_config, + priming_group_id=priming_group_id, + ) + assert str(detector) + assert isinstance(detector, Detector) + assert detector.mode == ModeEnum.COUNT + + +def test_create_multiclass_detector_with_priming_group_id(gl: Groundlight): + """ + Test creating a multiclass detector with a priming_group_id parameter. + """ + name = f"Test multiclass priming {datetime.utcnow()}" + query = "What type of animal is this?" + class_names = ["dog", "cat", "bird"] + pipeline_config = "never-review" + priming_group_id = "prgrp_test123456789012345678901234567890" + + detector = gl.create_multiclass_detector( + name=name, + query=query, + class_names=class_names, + pipeline_config=pipeline_config, + priming_group_id=priming_group_id, + ) + assert str(detector) + assert isinstance(detector, Detector) + assert detector.mode == ModeEnum.MULTI_CLASS + + +def test_create_detector_with_priming_group_id_and_all_params(gl: Groundlight): + """ + Test creating a detector with priming_group_id along with all other parameters. + """ + name = f"Test full priming {datetime.utcnow()}" + query = "Is there a dog?" + group_name = "Test group" + confidence_threshold = 0.825 + patience_time = 300 # seconds + pipeline_config = "never-review" + metadata = {"location": "test-lab", "experiment": "priming-test"} + priming_group_id = "prgrp_test123456789012345678901234567890" + + detector = gl.create_detector( + name=name, + query=query, + group_name=group_name, + confidence_threshold=confidence_threshold, + patience_time=patience_time, + pipeline_config=pipeline_config, + metadata=metadata, + priming_group_id=priming_group_id, + ) + assert isinstance(detector, Detector) + assert detector.name == name + assert detector.query == query + assert detector.group_name == group_name + assert detector.confidence_threshold == confidence_threshold + assert detector.metadata == metadata + + +def test_create_detector_modes_with_priming_group_id(gl: Groundlight): + """ + Test creating detectors in different modes with priming_group_id. + """ + priming_group_id = "prgrp_test123456789012345678901234567890" + pipeline_config = "never-review" + + # Test binary mode + name_binary = f"Test binary mode priming {datetime.utcnow()}" + binary_detector = gl.create_detector( + name=name_binary, + query="Is there a dog?", + mode=ModeEnum.BINARY, + pipeline_config=pipeline_config, + priming_group_id=priming_group_id, + ) + assert isinstance(binary_detector, Detector) + assert binary_detector.mode == ModeEnum.BINARY + + # Test count mode + name_count = f"Test count mode priming {datetime.utcnow()}" + count_detector = gl.create_detector( + name=name_count, + query="How many dogs?", + mode=ModeEnum.COUNT, + class_names="dog", + pipeline_config=pipeline_config, + priming_group_id=priming_group_id, + ) + assert isinstance(count_detector, Detector) + assert count_detector.mode == ModeEnum.COUNT + + # Test multiclass mode + name_multi = f"Test multiclass mode priming {datetime.utcnow()}" + multi_detector = gl.create_detector( + name=name_multi, + query="What animal?", + mode=ModeEnum.MULTI_CLASS, + class_names=["dog", "cat", "bird"], + pipeline_config=pipeline_config, + priming_group_id=priming_group_id, + ) + assert isinstance(multi_detector, Detector) + assert multi_detector.mode == ModeEnum.MULTI_CLASS diff --git a/test/unit/test_priming_group.py b/test/unit/test_priming_group.py new file mode 100644 index 00000000..762b2310 --- /dev/null +++ b/test/unit/test_priming_group.py @@ -0,0 +1,171 @@ +"""Unit tests for priming_group_id parameter in detector creation.""" +from unittest import mock + +import pytest +from groundlight import Groundlight +from model import Detector, ModeEnum + + +@pytest.fixture(name="gl") +def groundlight_fixture() -> Groundlight: + """Creates a Groundlight client.""" + return Groundlight() + + +@pytest.fixture(name="mock_detector_api") +def mock_detector_api_fixture(): + """Mock the detectors API.""" + with mock.patch("groundlight.client.DetectorsApi") as mock_api: + # Setup a mock detector response + mock_detector_response = mock.MagicMock() + mock_detector_response.to_dict.return_value = { + "id": "det_test123", + "name": "test detector", + "query": "Is there a dog?", + "type": "binary", + "created_at": "2024-01-01T00:00:00Z", + "group_name": "default", + "confidence_threshold": 0.9, + "mode": "BINARY", + } + mock_api.return_value.create_detector.return_value = mock_detector_response + yield mock_api + + +def test_create_detector_passes_priming_group_id(gl: Groundlight, mock_detector_api): + """Test that priming_group_id is passed through to the API when creating a detector.""" + priming_group_id = "prgrp_test123456789012345678901234567890" + + detector = gl.create_detector( + name="test detector", + query="Is there a dog?", + priming_group_id=priming_group_id, + ) + + # Verify the API was called + assert mock_detector_api.return_value.create_detector.called + + # Get the detector_creation_input that was passed to the API + call_args = mock_detector_api.return_value.create_detector.call_args + detector_creation_input = call_args[0][0] + + # Verify priming_group_id was set correctly + assert detector_creation_input.priming_group_id == priming_group_id + assert isinstance(detector, Detector) + + +def test_create_binary_detector_passes_priming_group_id(gl: Groundlight, mock_detector_api): + """Test that priming_group_id is passed through when creating a binary detector.""" + priming_group_id = "prgrp_test123456789012345678901234567890" + + detector = gl.create_binary_detector( + name="test binary detector", + query="Is there a cat?", + priming_group_id=priming_group_id, + ) + + # Verify the API was called + assert mock_detector_api.return_value.create_detector.called + + # Get the detector_creation_input that was passed to the API + call_args = mock_detector_api.return_value.create_detector.call_args + detector_creation_input = call_args[0][0] + + # Verify priming_group_id was set correctly + assert detector_creation_input.priming_group_id == priming_group_id + assert isinstance(detector, Detector) + + +def test_create_counting_detector_passes_priming_group_id(gl: Groundlight, mock_detector_api): + """Test that priming_group_id is passed through when creating a counting detector.""" + priming_group_id = "prgrp_test123456789012345678901234567890" + + # Update mock response for counting detector + mock_detector_response = mock.MagicMock() + mock_detector_response.to_dict.return_value = { + "id": "det_test456", + "name": "test counting detector", + "query": "How many dogs?", + "type": "count", + "created_at": "2024-01-01T00:00:00Z", + "group_name": "default", + "confidence_threshold": 0.9, + "mode": "COUNT", + "mode_configuration": {"class_name": "dog", "max_count": 10}, + } + mock_detector_api.return_value.create_detector.return_value = mock_detector_response + + detector = gl.create_counting_detector( + name="test counting detector", + query="How many dogs?", + class_name="dog", + priming_group_id=priming_group_id, + ) + + # Verify the API was called + assert mock_detector_api.return_value.create_detector.called + + # Get the detector_creation_input that was passed to the API + call_args = mock_detector_api.return_value.create_detector.call_args + detector_creation_input = call_args[0][0] + + # Verify priming_group_id was set correctly + assert detector_creation_input.priming_group_id == priming_group_id + assert isinstance(detector, Detector) + + +def test_create_multiclass_detector_passes_priming_group_id(gl: Groundlight, mock_detector_api): + """Test that priming_group_id is passed through when creating a multiclass detector.""" + priming_group_id = "prgrp_test123456789012345678901234567890" + + # Update mock response for multiclass detector + mock_detector_response = mock.MagicMock() + mock_detector_response.to_dict.return_value = { + "id": "det_test789", + "name": "test multiclass detector", + "query": "What animal?", + "type": "multiclass", + "created_at": "2024-01-01T00:00:00Z", + "group_name": "default", + "confidence_threshold": 0.9, + "mode": "MULTI_CLASS", + "mode_configuration": {"class_names": ["dog", "cat", "bird"]}, + } + mock_detector_api.return_value.create_detector.return_value = mock_detector_response + + detector = gl.create_multiclass_detector( + name="test multiclass detector", + query="What animal?", + class_names=["dog", "cat", "bird"], + priming_group_id=priming_group_id, + ) + + # Verify the API was called + assert mock_detector_api.return_value.create_detector.called + + # Get the detector_creation_input that was passed to the API + call_args = mock_detector_api.return_value.create_detector.call_args + detector_creation_input = call_args[0][0] + + # Verify priming_group_id was set correctly + assert detector_creation_input.priming_group_id == priming_group_id + assert isinstance(detector, Detector) + + +def test_create_detector_without_priming_group_id(gl: Groundlight, mock_detector_api): + """Test that detector creation works without priming_group_id (backwards compatibility).""" + detector = gl.create_detector( + name="test detector no priming", + query="Is there a dog?", + ) + + # Verify the API was called + assert mock_detector_api.return_value.create_detector.called + + # Get the detector_creation_input that was passed to the API + call_args = mock_detector_api.return_value.create_detector.call_args + detector_creation_input = call_args[0][0] + + # Verify priming_group_id was not set (None or not present) + assert not hasattr(detector_creation_input, 'priming_group_id') or detector_creation_input.priming_group_id is None + assert isinstance(detector, Detector) From 5649fd02c4c572140d409ce39e78657d951d0732 Mon Sep 17 00:00:00 2001 From: Auto-format Bot Date: Tue, 10 Mar 2026 19:00:15 +0000 Subject: [PATCH 2/4] Automatically reformatting code --- test/unit/test_priming_group.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/unit/test_priming_group.py b/test/unit/test_priming_group.py index 762b2310..79d57969 100644 --- a/test/unit/test_priming_group.py +++ b/test/unit/test_priming_group.py @@ -1,9 +1,10 @@ """Unit tests for priming_group_id parameter in detector creation.""" + from unittest import mock import pytest from groundlight import Groundlight -from model import Detector, ModeEnum +from model import Detector @pytest.fixture(name="gl") @@ -167,5 +168,5 @@ def test_create_detector_without_priming_group_id(gl: Groundlight, mock_detector detector_creation_input = call_args[0][0] # Verify priming_group_id was not set (None or not present) - assert not hasattr(detector_creation_input, 'priming_group_id') or detector_creation_input.priming_group_id is None + assert not hasattr(detector_creation_input, "priming_group_id") or detector_creation_input.priming_group_id is None assert isinstance(detector, Detector) From 288c9ba661ff337b53edc3a5371bca380ecce99e Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 10 Mar 2026 19:50:56 +0000 Subject: [PATCH 3/4] update experimental as well --- src/groundlight/experimental_api.py | 9 ++++ test/unit/test_experimental.py | 33 ++++++++++++ test/unit/test_priming_group.py | 81 +++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+) diff --git a/src/groundlight/experimental_api.py b/src/groundlight/experimental_api.py index 90ef7470..5282871d 100644 --- a/src/groundlight/experimental_api.py +++ b/src/groundlight/experimental_api.py @@ -644,6 +644,7 @@ def create_bounding_box_detector( # noqa: PLR0913 # pylint: disable=too-many-ar patience_time: Optional[float] = None, pipeline_config: Optional[str] = None, metadata: Union[dict, str, None] = None, + priming_group_id: Optional[str] = None, ) -> Detector: """ Creates a bounding box detector that can detect objects in images up to a specified maximum number of bounding @@ -684,6 +685,7 @@ def create_bounding_box_detector( # noqa: PLR0913 # pylint: disable=too-many-ar the detector (limited to 1KB). This metadata can be used to store additional information like location, purpose, or related system IDs. You can retrieve this metadata later by calling `get_detector()`. + :param priming_group_id: Optional ID of an existing PrimingGroup to associate with this detector. :return: The created Detector object """ @@ -696,6 +698,7 @@ def create_bounding_box_detector( # noqa: PLR0913 # pylint: disable=too-many-ar patience_time=patience_time, pipeline_config=pipeline_config, metadata=metadata, + priming_group_id=priming_group_id, ) detector_creation_input.mode = ModeEnum.BOUNDING_BOX @@ -718,6 +721,7 @@ def create_text_recognition_detector( # noqa: PLR0913 # pylint: disable=too-man patience_time: Optional[float] = None, pipeline_config: Optional[str] = None, metadata: Union[dict, str, None] = None, + priming_group_id: Optional[str] = None, ) -> Detector: """ Creates a text recognition detector that can read specified spans of text from images. @@ -742,6 +746,10 @@ def create_text_recognition_detector( # noqa: PLR0913 # pylint: disable=too-man :param pipeline_config: Advanced usage only. Configuration string needed to instantiate a specific prediction pipeline for this detector. :param metadata: A dictionary or JSON string containing custom key/value pairs to associate with + the detector (limited to 1KB). This metadata can be used to store additional + information like location, purpose, or related system IDs. You can retrieve this + metadata later by calling `get_detector()`. + :param priming_group_id: Optional ID of an existing PrimingGroup to associate with this detector. :return: The created Detector object """ @@ -754,6 +762,7 @@ def create_text_recognition_detector( # noqa: PLR0913 # pylint: disable=too-man patience_time=patience_time, pipeline_config=pipeline_config, metadata=metadata, + priming_group_id=priming_group_id, ) detector_creation_input.mode = ModeEnum.TEXT mode_config = TextModeConfiguration() diff --git a/test/unit/test_experimental.py b/test/unit/test_experimental.py index 2b8f55f7..9a5940c5 100644 --- a/test/unit/test_experimental.py +++ b/test/unit/test_experimental.py @@ -138,3 +138,36 @@ def test_bounding_box_detector_async(gl_experimental: ExperimentalApi): # you should be able to get a "real" result by retrieving an updated image query object from the server _image_query = gl_experimental.get_image_query(id=async_iq.id) assert _image_query.result is not None + + +def test_text_recognition_detector_with_priming_group_id(gl_experimental: ExperimentalApi): + """ + Verify that we can create a text recognition detector with priming_group_id parameter + """ + name = f"Test priming text {datetime.utcnow()}" + priming_group_id = "prgrp_test123456789012345678901234567890" + created_detector = gl_experimental.create_text_recognition_detector( + name, + "What is the date and time?", + confidence_threshold=0.0, + priming_group_id=priming_group_id, + ) + assert created_detector is not None + assert isinstance(created_detector, Detector) + + +def test_bounding_box_detector_with_priming_group_id(gl_experimental: ExperimentalApi): + """ + Verify that we can create a bounding box detector with priming_group_id parameter + """ + name = f"Test priming bbox {datetime.now(timezone.utc)}" + priming_group_id = "prgrp_test123456789012345678901234567890" + created_detector = gl_experimental.create_bounding_box_detector( + name, + "Draw a bounding box around each dog in the image", + "dog", + confidence_threshold=0.0, + priming_group_id=priming_group_id, + ) + assert created_detector is not None + assert isinstance(created_detector, Detector) diff --git a/test/unit/test_priming_group.py b/test/unit/test_priming_group.py index 762b2310..bcf0aca9 100644 --- a/test/unit/test_priming_group.py +++ b/test/unit/test_priming_group.py @@ -169,3 +169,84 @@ def test_create_detector_without_priming_group_id(gl: Groundlight, mock_detector # Verify priming_group_id was not set (None or not present) assert not hasattr(detector_creation_input, 'priming_group_id') or detector_creation_input.priming_group_id is None assert isinstance(detector, Detector) + + +def test_create_bounding_box_detector_passes_priming_group_id(mock_detector_api): + """Test that priming_group_id is passed through when creating a bounding box detector.""" + from groundlight import ExperimentalApi + + gl_exp = ExperimentalApi() + priming_group_id = "prgrp_test123456789012345678901234567890" + + # Update mock response for bounding box detector + mock_detector_response = mock.MagicMock() + mock_detector_response.to_dict.return_value = { + "id": "det_testbbox", + "name": "test bbox detector", + "query": "Draw boxes around dogs", + "type": "bounding_box", + "created_at": "2024-01-01T00:00:00Z", + "group_name": "default", + "confidence_threshold": 0.9, + "mode": "BOUNDING_BOX", + "mode_configuration": {"class_name": "dog", "max_num_bboxes": 10}, + } + mock_detector_api.return_value.create_detector.return_value = mock_detector_response + + detector = gl_exp.create_bounding_box_detector( + name="test bbox detector", + query="Draw boxes around dogs", + class_name="dog", + priming_group_id=priming_group_id, + ) + + # Verify the API was called + assert mock_detector_api.return_value.create_detector.called + + # Get the detector_creation_input that was passed to the API + call_args = mock_detector_api.return_value.create_detector.call_args + detector_creation_input = call_args[0][0] + + # Verify priming_group_id was set correctly + assert detector_creation_input.priming_group_id == priming_group_id + assert isinstance(detector, Detector) + + +def test_create_text_recognition_detector_passes_priming_group_id(mock_detector_api): + """Test that priming_group_id is passed through when creating a text recognition detector.""" + from groundlight import ExperimentalApi + + gl_exp = ExperimentalApi() + priming_group_id = "prgrp_test123456789012345678901234567890" + + # Update mock response for text recognition detector + mock_detector_response = mock.MagicMock() + mock_detector_response.to_dict.return_value = { + "id": "det_testtext", + "name": "test text detector", + "query": "Read the text", + "type": "text", + "created_at": "2024-01-01T00:00:00Z", + "group_name": "default", + "confidence_threshold": 0.9, + "mode": "TEXT", + "mode_configuration": {}, + } + mock_detector_api.return_value.create_detector.return_value = mock_detector_response + + detector = gl_exp.create_text_recognition_detector( + name="test text detector", + query="Read the text", + priming_group_id=priming_group_id, + ) + + # Verify the API was called + assert mock_detector_api.return_value.create_detector.called + + # Get the detector_creation_input that was passed to the API + call_args = mock_detector_api.return_value.create_detector.call_args + detector_creation_input = call_args[0][0] + + # Verify priming_group_id was set correctly + assert detector_creation_input.priming_group_id == priming_group_id + assert isinstance(detector, Detector) From 65bab3f0a70ab8be7db7ba3e68f2e02a9c08e31a Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 10 Mar 2026 22:14:50 +0000 Subject: [PATCH 4/4] fix tests --- test/integration/test_groundlight.py | 167 ++---------------- test/unit/test_experimental.py | 33 ---- test/unit/test_priming_group.py | 253 --------------------------- 3 files changed, 13 insertions(+), 440 deletions(-) delete mode 100644 test/unit/test_priming_group.py diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 704f296c..988386a1 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -939,163 +939,22 @@ def test_delete_detector(gl: Groundlight): gl.delete_detector(fake_detector_id) # type: ignore -def test_create_detector_with_priming_group_id(gl: Groundlight): +def test_create_detector_with_invalid_priming_group_id(gl: Groundlight): """ - Test creating a detector with a priming_group_id parameter. + Test that creating a detector with a non-existent priming_group_id returns an appropriate error. """ - name = f"Test priming group {datetime.utcnow()}" + name = f"Test invalid priming {datetime.utcnow()}" query = "Is there a dog?" pipeline_config = "never-review" - # Using a sample priming group ID format - priming_group_id = "prgrp_test123456789012345678901234567890" + priming_group_id = "prgrp_nonexistent12345678901234567890" - detector = gl.create_detector( - name=name, - query=query, - pipeline_config=pipeline_config, - priming_group_id=priming_group_id, - ) - assert str(detector) - assert isinstance(detector, Detector) - # Note: priming_group_id might not be returned in the detector object - # depending on the API implementation - - -def test_create_binary_detector_with_priming_group_id(gl: Groundlight): - """ - Test creating a binary detector with a priming_group_id parameter. - """ - name = f"Test binary priming {datetime.utcnow()}" - query = "Is there a cat?" - pipeline_config = "never-review" - priming_group_id = "prgrp_test123456789012345678901234567890" - - detector = gl.create_binary_detector( - name=name, - query=query, - pipeline_config=pipeline_config, - priming_group_id=priming_group_id, - ) - assert str(detector) - assert isinstance(detector, Detector) - - -def test_create_counting_detector_with_priming_group_id(gl: Groundlight): - """ - Test creating a counting detector with a priming_group_id parameter. - """ - name = f"Test counting priming {datetime.utcnow()}" - query = "How many dogs are present?" - class_name = "dog" - pipeline_config = "never-review" - priming_group_id = "prgrp_test123456789012345678901234567890" - - detector = gl.create_counting_detector( - name=name, - query=query, - class_name=class_name, - pipeline_config=pipeline_config, - priming_group_id=priming_group_id, - ) - assert str(detector) - assert isinstance(detector, Detector) - assert detector.mode == ModeEnum.COUNT - - -def test_create_multiclass_detector_with_priming_group_id(gl: Groundlight): - """ - Test creating a multiclass detector with a priming_group_id parameter. - """ - name = f"Test multiclass priming {datetime.utcnow()}" - query = "What type of animal is this?" - class_names = ["dog", "cat", "bird"] - pipeline_config = "never-review" - priming_group_id = "prgrp_test123456789012345678901234567890" - - detector = gl.create_multiclass_detector( - name=name, - query=query, - class_names=class_names, - pipeline_config=pipeline_config, - priming_group_id=priming_group_id, - ) - assert str(detector) - assert isinstance(detector, Detector) - assert detector.mode == ModeEnum.MULTI_CLASS - - -def test_create_detector_with_priming_group_id_and_all_params(gl: Groundlight): - """ - Test creating a detector with priming_group_id along with all other parameters. - """ - name = f"Test full priming {datetime.utcnow()}" - query = "Is there a dog?" - group_name = "Test group" - confidence_threshold = 0.825 - patience_time = 300 # seconds - pipeline_config = "never-review" - metadata = {"location": "test-lab", "experiment": "priming-test"} - priming_group_id = "prgrp_test123456789012345678901234567890" - - detector = gl.create_detector( - name=name, - query=query, - group_name=group_name, - confidence_threshold=confidence_threshold, - patience_time=patience_time, - pipeline_config=pipeline_config, - metadata=metadata, - priming_group_id=priming_group_id, - ) - assert isinstance(detector, Detector) - assert detector.name == name - assert detector.query == query - assert detector.group_name == group_name - assert detector.confidence_threshold == confidence_threshold - assert detector.metadata == metadata - - -def test_create_detector_modes_with_priming_group_id(gl: Groundlight): - """ - Test creating detectors in different modes with priming_group_id. - """ - priming_group_id = "prgrp_test123456789012345678901234567890" - pipeline_config = "never-review" + with pytest.raises(NotFoundException) as exc_info: + gl.create_detector( + name=name, + query=query, + pipeline_config=pipeline_config, + priming_group_id=priming_group_id, + ) - # Test binary mode - name_binary = f"Test binary mode priming {datetime.utcnow()}" - binary_detector = gl.create_detector( - name=name_binary, - query="Is there a dog?", - mode=ModeEnum.BINARY, - pipeline_config=pipeline_config, - priming_group_id=priming_group_id, - ) - assert isinstance(binary_detector, Detector) - assert binary_detector.mode == ModeEnum.BINARY - - # Test count mode - name_count = f"Test count mode priming {datetime.utcnow()}" - count_detector = gl.create_detector( - name=name_count, - query="How many dogs?", - mode=ModeEnum.COUNT, - class_names="dog", - pipeline_config=pipeline_config, - priming_group_id=priming_group_id, - ) - assert isinstance(count_detector, Detector) - assert count_detector.mode == ModeEnum.COUNT - - # Test multiclass mode - name_multi = f"Test multiclass mode priming {datetime.utcnow()}" - multi_detector = gl.create_detector( - name=name_multi, - query="What animal?", - mode=ModeEnum.MULTI_CLASS, - class_names=["dog", "cat", "bird"], - pipeline_config=pipeline_config, - priming_group_id=priming_group_id, - ) - assert isinstance(multi_detector, Detector) - assert multi_detector.mode == ModeEnum.MULTI_CLASS + # Verify the error message mentions PrimingGroup + assert "PrimingGroup" in str(exc_info.value) diff --git a/test/unit/test_experimental.py b/test/unit/test_experimental.py index 9a5940c5..2b8f55f7 100644 --- a/test/unit/test_experimental.py +++ b/test/unit/test_experimental.py @@ -138,36 +138,3 @@ def test_bounding_box_detector_async(gl_experimental: ExperimentalApi): # you should be able to get a "real" result by retrieving an updated image query object from the server _image_query = gl_experimental.get_image_query(id=async_iq.id) assert _image_query.result is not None - - -def test_text_recognition_detector_with_priming_group_id(gl_experimental: ExperimentalApi): - """ - Verify that we can create a text recognition detector with priming_group_id parameter - """ - name = f"Test priming text {datetime.utcnow()}" - priming_group_id = "prgrp_test123456789012345678901234567890" - created_detector = gl_experimental.create_text_recognition_detector( - name, - "What is the date and time?", - confidence_threshold=0.0, - priming_group_id=priming_group_id, - ) - assert created_detector is not None - assert isinstance(created_detector, Detector) - - -def test_bounding_box_detector_with_priming_group_id(gl_experimental: ExperimentalApi): - """ - Verify that we can create a bounding box detector with priming_group_id parameter - """ - name = f"Test priming bbox {datetime.now(timezone.utc)}" - priming_group_id = "prgrp_test123456789012345678901234567890" - created_detector = gl_experimental.create_bounding_box_detector( - name, - "Draw a bounding box around each dog in the image", - "dog", - confidence_threshold=0.0, - priming_group_id=priming_group_id, - ) - assert created_detector is not None - assert isinstance(created_detector, Detector) diff --git a/test/unit/test_priming_group.py b/test/unit/test_priming_group.py deleted file mode 100644 index 9edc90b2..00000000 --- a/test/unit/test_priming_group.py +++ /dev/null @@ -1,253 +0,0 @@ -"""Unit tests for priming_group_id parameter in detector creation.""" - -from unittest import mock - -import pytest -from groundlight import Groundlight -from model import Detector - - -@pytest.fixture(name="gl") -def groundlight_fixture() -> Groundlight: - """Creates a Groundlight client.""" - return Groundlight() - - -@pytest.fixture(name="mock_detector_api") -def mock_detector_api_fixture(): - """Mock the detectors API.""" - with mock.patch("groundlight.client.DetectorsApi") as mock_api: - # Setup a mock detector response - mock_detector_response = mock.MagicMock() - mock_detector_response.to_dict.return_value = { - "id": "det_test123", - "name": "test detector", - "query": "Is there a dog?", - "type": "binary", - "created_at": "2024-01-01T00:00:00Z", - "group_name": "default", - "confidence_threshold": 0.9, - "mode": "BINARY", - } - mock_api.return_value.create_detector.return_value = mock_detector_response - yield mock_api - - -def test_create_detector_passes_priming_group_id(gl: Groundlight, mock_detector_api): - """Test that priming_group_id is passed through to the API when creating a detector.""" - priming_group_id = "prgrp_test123456789012345678901234567890" - - detector = gl.create_detector( - name="test detector", - query="Is there a dog?", - priming_group_id=priming_group_id, - ) - - # Verify the API was called - assert mock_detector_api.return_value.create_detector.called - - # Get the detector_creation_input that was passed to the API - call_args = mock_detector_api.return_value.create_detector.call_args - detector_creation_input = call_args[0][0] - - # Verify priming_group_id was set correctly - assert detector_creation_input.priming_group_id == priming_group_id - assert isinstance(detector, Detector) - - -def test_create_binary_detector_passes_priming_group_id(gl: Groundlight, mock_detector_api): - """Test that priming_group_id is passed through when creating a binary detector.""" - priming_group_id = "prgrp_test123456789012345678901234567890" - - detector = gl.create_binary_detector( - name="test binary detector", - query="Is there a cat?", - priming_group_id=priming_group_id, - ) - - # Verify the API was called - assert mock_detector_api.return_value.create_detector.called - - # Get the detector_creation_input that was passed to the API - call_args = mock_detector_api.return_value.create_detector.call_args - detector_creation_input = call_args[0][0] - - # Verify priming_group_id was set correctly - assert detector_creation_input.priming_group_id == priming_group_id - assert isinstance(detector, Detector) - - -def test_create_counting_detector_passes_priming_group_id(gl: Groundlight, mock_detector_api): - """Test that priming_group_id is passed through when creating a counting detector.""" - priming_group_id = "prgrp_test123456789012345678901234567890" - - # Update mock response for counting detector - mock_detector_response = mock.MagicMock() - mock_detector_response.to_dict.return_value = { - "id": "det_test456", - "name": "test counting detector", - "query": "How many dogs?", - "type": "count", - "created_at": "2024-01-01T00:00:00Z", - "group_name": "default", - "confidence_threshold": 0.9, - "mode": "COUNT", - "mode_configuration": {"class_name": "dog", "max_count": 10}, - } - mock_detector_api.return_value.create_detector.return_value = mock_detector_response - - detector = gl.create_counting_detector( - name="test counting detector", - query="How many dogs?", - class_name="dog", - priming_group_id=priming_group_id, - ) - - # Verify the API was called - assert mock_detector_api.return_value.create_detector.called - - # Get the detector_creation_input that was passed to the API - call_args = mock_detector_api.return_value.create_detector.call_args - detector_creation_input = call_args[0][0] - - # Verify priming_group_id was set correctly - assert detector_creation_input.priming_group_id == priming_group_id - assert isinstance(detector, Detector) - - -def test_create_multiclass_detector_passes_priming_group_id(gl: Groundlight, mock_detector_api): - """Test that priming_group_id is passed through when creating a multiclass detector.""" - priming_group_id = "prgrp_test123456789012345678901234567890" - - # Update mock response for multiclass detector - mock_detector_response = mock.MagicMock() - mock_detector_response.to_dict.return_value = { - "id": "det_test789", - "name": "test multiclass detector", - "query": "What animal?", - "type": "multiclass", - "created_at": "2024-01-01T00:00:00Z", - "group_name": "default", - "confidence_threshold": 0.9, - "mode": "MULTI_CLASS", - "mode_configuration": {"class_names": ["dog", "cat", "bird"]}, - } - mock_detector_api.return_value.create_detector.return_value = mock_detector_response - - detector = gl.create_multiclass_detector( - name="test multiclass detector", - query="What animal?", - class_names=["dog", "cat", "bird"], - priming_group_id=priming_group_id, - ) - - # Verify the API was called - assert mock_detector_api.return_value.create_detector.called - - # Get the detector_creation_input that was passed to the API - call_args = mock_detector_api.return_value.create_detector.call_args - detector_creation_input = call_args[0][0] - - # Verify priming_group_id was set correctly - assert detector_creation_input.priming_group_id == priming_group_id - assert isinstance(detector, Detector) - - -def test_create_detector_without_priming_group_id(gl: Groundlight, mock_detector_api): - """Test that detector creation works without priming_group_id (backwards compatibility).""" - detector = gl.create_detector( - name="test detector no priming", - query="Is there a dog?", - ) - - # Verify the API was called - assert mock_detector_api.return_value.create_detector.called - - # Get the detector_creation_input that was passed to the API - call_args = mock_detector_api.return_value.create_detector.call_args - detector_creation_input = call_args[0][0] - - # Verify priming_group_id was not set (None or not present) - assert not hasattr(detector_creation_input, "priming_group_id") or detector_creation_input.priming_group_id is None - assert isinstance(detector, Detector) - - -def test_create_bounding_box_detector_passes_priming_group_id(mock_detector_api): - """Test that priming_group_id is passed through when creating a bounding box detector.""" - from groundlight import ExperimentalApi - - gl_exp = ExperimentalApi() - priming_group_id = "prgrp_test123456789012345678901234567890" - - # Update mock response for bounding box detector - mock_detector_response = mock.MagicMock() - mock_detector_response.to_dict.return_value = { - "id": "det_testbbox", - "name": "test bbox detector", - "query": "Draw boxes around dogs", - "type": "bounding_box", - "created_at": "2024-01-01T00:00:00Z", - "group_name": "default", - "confidence_threshold": 0.9, - "mode": "BOUNDING_BOX", - "mode_configuration": {"class_name": "dog", "max_num_bboxes": 10}, - } - mock_detector_api.return_value.create_detector.return_value = mock_detector_response - - detector = gl_exp.create_bounding_box_detector( - name="test bbox detector", - query="Draw boxes around dogs", - class_name="dog", - priming_group_id=priming_group_id, - ) - - # Verify the API was called - assert mock_detector_api.return_value.create_detector.called - - # Get the detector_creation_input that was passed to the API - call_args = mock_detector_api.return_value.create_detector.call_args - detector_creation_input = call_args[0][0] - - # Verify priming_group_id was set correctly - assert detector_creation_input.priming_group_id == priming_group_id - assert isinstance(detector, Detector) - - -def test_create_text_recognition_detector_passes_priming_group_id(mock_detector_api): - """Test that priming_group_id is passed through when creating a text recognition detector.""" - from groundlight import ExperimentalApi - - gl_exp = ExperimentalApi() - priming_group_id = "prgrp_test123456789012345678901234567890" - - # Update mock response for text recognition detector - mock_detector_response = mock.MagicMock() - mock_detector_response.to_dict.return_value = { - "id": "det_testtext", - "name": "test text detector", - "query": "Read the text", - "type": "text", - "created_at": "2024-01-01T00:00:00Z", - "group_name": "default", - "confidence_threshold": 0.9, - "mode": "TEXT", - "mode_configuration": {}, - } - mock_detector_api.return_value.create_detector.return_value = mock_detector_response - - detector = gl_exp.create_text_recognition_detector( - name="test text detector", - query="Read the text", - priming_group_id=priming_group_id, - ) - - # Verify the API was called - assert mock_detector_api.return_value.create_detector.called - - # Get the detector_creation_input that was passed to the API - call_args = mock_detector_api.return_value.create_detector.call_args - detector_creation_input = call_args[0][0] - - # Verify priming_group_id was set correctly - assert detector_creation_input.priming_group_id == priming_group_id - assert isinstance(detector, Detector)