From 2b09e7936215208f03682205800d48e0dc4e21f1 Mon Sep 17 00:00:00 2001 From: Tim Huff Date: Wed, 18 Mar 2026 19:10:45 +0000 Subject: [PATCH 1/9] fixing flaky test --- test/unit/test_http_retries.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit/test_http_retries.py b/test/unit/test_http_retries.py index 42ab0caf..106c871f 100644 --- a/test/unit/test_http_retries.py +++ b/test/unit/test_http_retries.py @@ -1,6 +1,7 @@ from datetime import datetime from typing import Any, Callable from unittest import mock +from uuid import uuid4 import pytest from groundlight import Groundlight @@ -9,7 +10,7 @@ from model import Detector DEFAULT_CONFIDENCE_THRESHOLD = 0.9 -DETECTOR_NAME = f"test detector_{datetime.utcnow().strftime('%Y=%m-%d %H:%M:%S')}" +DETECTOR_NAME = f"test detector_{datetime.utcnow().strftime('%Y=%m-%d %H:%M:%S')}_{uuid4().hex[:8]}" TOTAL_RETRIES = 3 STATUS_CODES = range(500, 505) IMAGE_FILE = "test/assets/dog.jpeg" From ee49af58db1c8347e96e494d04531ab3a98c9237 Mon Sep 17 00:00:00 2001 From: Tim Huff Date: Wed, 18 Mar 2026 21:12:00 +0000 Subject: [PATCH 2/9] applying the fix in more places --- test/conftest.py | 12 +++-- test/integration/test_groundlight.py | 44 +++++++++---------- .../integration/test_groundlight_expensive.py | 8 ++-- test/unit/test_cli.py | 5 ++- test/unit/test_detector_reset.py | 6 +-- test/unit/test_experimental.py | 16 +++---- test/unit/test_http_retries.py | 5 +-- 7 files changed, 50 insertions(+), 46 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index bde11be8..0bfe06e0 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,10 +1,16 @@ from datetime import datetime +from uuid import uuid4 import pytest from groundlight import ExperimentalApi, Groundlight from model import Detector, ImageQuery, ImageQueryTypeEnum, ResultTypeEnum +def generate_test_detector_name(prefix: str = "Test") -> str: + """Generates a unique detector name with a timestamp and random suffix to avoid collisions in parallel CI.""" + return f"{prefix} {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')}_{uuid4().hex[:8]}" + + def pytest_configure(config): # pylint: disable=unused-argument # Run environment check before tests gl = Groundlight() @@ -25,20 +31,18 @@ def fixture_gl() -> Groundlight: @pytest.fixture(name="detector") def fixture_detector(gl: Groundlight) -> Detector: """Creates a new Test detector.""" - name = f"Test {datetime.utcnow()}" # Need a unique name query = "Is there a dog?" pipeline_config = "never-review" - return gl.create_detector(name=name, query=query, pipeline_config=pipeline_config) + return gl.create_detector(name=generate_test_detector_name(), query=query, pipeline_config=pipeline_config) @pytest.fixture(name="count_detector") def fixture_count_detector(gl_experimental: ExperimentalApi) -> Detector: """Creates a new Test detector.""" - name = f"Test {datetime.utcnow()}" # Need a unique name query = "How many dogs?" pipeline_config = "never-review-multi" # always predicts 0 return gl_experimental.create_counting_detector( - name=name, query=query, class_name="dog", pipeline_config=pipeline_config + name=generate_test_detector_name(), query=query, class_name="dog", pipeline_config=pipeline_config ) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 5d48c1cd..4cbd7f7d 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -5,10 +5,10 @@ import random import string import time -from datetime import datetime from typing import Any, Dict, Optional, Union import pytest +from conftest import generate_test_detector_name from groundlight import Groundlight from groundlight.binary_labels import VALID_DISPLAY_LABELS, Label, convert_internal_label_to_display from groundlight.internalapi import ApiException, InternalApiError, NotFoundError @@ -97,7 +97,7 @@ def test_create_groundlight_with_retries(): def test_create_detector(gl: Groundlight): - name = f"Test {datetime.utcnow()}" # Need a unique name + name = generate_test_detector_name() query = "Is there a dog?" _detector = gl.create_detector(name=name, query=query) assert str(_detector) @@ -107,12 +107,12 @@ def test_create_detector(gl: Groundlight): ), "We expected the default confidence threshold to be used." # Test creating dectors with other modes - name = f"Test {datetime.utcnow()}" # Need a unique name - count_detector = gl.create_detector(name=name, query=query, mode=ModeEnum.COUNT, class_names="dog") + count_detector = gl.create_detector( + name=generate_test_detector_name(), query=query, mode=ModeEnum.COUNT, class_names="dog" + ) assert str(count_detector) - name = f"Test {datetime.utcnow()}" # Need a unique name multiclass_detector = gl.create_detector( - name=name, query=query, mode=ModeEnum.MULTI_CLASS, class_names=["dog", "cat"] + name=generate_test_detector_name(), query=query, mode=ModeEnum.MULTI_CLASS, class_names=["dog", "cat"] ) assert str(multiclass_detector) @@ -120,7 +120,7 @@ def test_create_detector(gl: Groundlight): def test_create_detector_with_pipeline_config(gl: Groundlight): # "never-review" is a special model that always returns the same result with 100% confidence. # It's useful for testing. - name = f"Test never-review {datetime.utcnow()}" # Need a unique name + name = generate_test_detector_name("Test never-review") query = "Is there a dog (always-pass)?" pipeline_config = "never-review" _detector = gl.create_detector(name=name, query=query, pipeline_config=pipeline_config) @@ -129,7 +129,7 @@ def test_create_detector_with_pipeline_config(gl: Groundlight): def test_create_detector_with_edge_pipeline_config(gl: Groundlight): - name = f"Test edge-pipeline-config {datetime.utcnow()}" + name = generate_test_detector_name("Test edge-pipeline-config") query = "Is there a dog (edge-config)?" _detector = gl.create_detector( name=name, @@ -144,7 +144,7 @@ def test_create_detector_with_edge_pipeline_config(gl: Groundlight): def test_create_detector_with_confidence_threshold(gl: Groundlight): # "never-review" is a special model that always returns the same result with 100% confidence. # It's useful for testing. - name = f"Test with confidence {datetime.utcnow()}" # Need a unique name + name = generate_test_detector_name("Test with confidence") query = "Is there a dog in the image?" pipeline_config = "never-review" confidence_threshold = 0.825 @@ -198,7 +198,7 @@ def test_create_detector_with_confidence_threshold(gl: Groundlight): @pytest.mark.skip_for_edge_endpoint(reason="The edge-endpoint does not support passing detector metadata.") def test_create_detector_with_everything(gl: Groundlight): - name = f"Test {datetime.utcnow()}" # Need a unique name + name = generate_test_detector_name() query = "Is there a dog?" group_name = "Test group" confidence_threshold = 0.825 @@ -234,7 +234,7 @@ def test_list_detectors(gl: Groundlight): def test_get_or_create_detector(gl: Groundlight): # With a unique name, we should be creating a new detector. - unique_name = f"Unique name {datetime.utcnow()}" + unique_name = generate_test_detector_name() query = "Is there a dog?" detector = gl.get_or_create_detector(name=unique_name, query=query) assert str(detector) @@ -411,7 +411,7 @@ def test_submit_image_query_with_low_request_timeout(gl: Groundlight, detector: @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 + name = generate_test_detector_name() query = "Is there a dog?" metadata = generate_random_dict(target_size_bytes=200) detector = gl.create_detector(name=name, query=query, metadata=metadata) @@ -423,7 +423,7 @@ def test_create_detector_with_metadata(gl: Groundlight): @pytest.mark.skip_for_edge_endpoint(reason="The edge-endpoint does not support passing detector metadata.") def test_get_or_create_detector_with_metadata(gl: Groundlight): - unique_name = f"Unique name {datetime.utcnow()}" + unique_name = generate_test_detector_name() query = "Is there a dog?" metadata = generate_random_dict(target_size_bytes=200) detector = gl.get_or_create_detector(name=unique_name, query=query, metadata=metadata) @@ -444,7 +444,7 @@ def test_get_or_create_detector_with_metadata(gl: Groundlight): ], ) def test_create_detector_with_invalid_metadata(gl: Groundlight, metadata_list: Any): - name = f"Test {datetime.utcnow()}" # Need a unique name + name = generate_test_detector_name() query = "Is there a dog?" for metadata in metadata_list: @@ -629,7 +629,7 @@ def test_list_image_queries(gl: Groundlight): def test_list_image_queries_with_filter(gl: Groundlight): # We want a fresh detector so we know exactly what image queries are associated with it - detector = gl.create_detector(name=f"Test {datetime.utcnow()}", query="Is there a dog?") + detector = gl.create_detector(name=generate_test_detector_name(), query="Is there a dog?") image_query_yes = gl.ask_async(detector=detector.id, image="test/assets/dog.jpeg", human_review="NEVER") image_query_no = gl.ask_async(detector=detector.id, image="test/assets/cat.jpeg", human_review="NEVER") iq_ids = [image_query_yes.id, image_query_no.id] @@ -859,7 +859,7 @@ def test_binary_detector(gl: Groundlight): """ verify that we can create and submit to a binary detector """ - name = f"Test {datetime.utcnow()}" + name = generate_test_detector_name() created_detector = gl.create_binary_detector(name, "Is there a dog", confidence_threshold=0.0) assert created_detector is not None binary_iq = gl.submit_image_query(created_detector, "test/assets/dog.jpeg") @@ -870,7 +870,7 @@ def test_counting_detector(gl: Groundlight): """ verify that we can create and submit to a counting detector """ - name = f"Test {datetime.utcnow()}" + name = generate_test_detector_name() created_detector = gl.create_counting_detector(name, "How many dogs", "dog", confidence_threshold=0.0) assert created_detector is not None count_iq = gl.submit_image_query(created_detector, "test/assets/dog.jpeg") @@ -881,7 +881,7 @@ def test_counting_detector_async(gl: Groundlight): """ verify that we can create and submit to a counting detector """ - name = f"Test {datetime.utcnow()}" + name = generate_test_detector_name() created_detector = gl.create_counting_detector(name, "How many dogs", "dog", confidence_threshold=0.0) assert created_detector is not None async_iq = gl.ask_async(created_detector, "test/assets/dog.jpeg") @@ -900,7 +900,7 @@ def test_multiclass_detector(gl: Groundlight): """ verify that we can create and submit to a multi-class detector """ - name = f"Test {datetime.utcnow()}" + name = generate_test_detector_name() class_names = ["Golden Retriever", "Labrador Retriever", "Poodle"] created_detector = gl.create_multiclass_detector( name, "What kind of dog is this?", class_names=class_names, confidence_threshold=0.0 @@ -916,7 +916,7 @@ def test_delete_detector(gl: Groundlight): Test deleting a detector by both ID and object, and verify proper error handling. """ # Create a detector to delete - name = f"Test delete detector {datetime.utcnow()}" + name = generate_test_detector_name("Test delete detector") query = "Is there a dog to delete?" pipeline_config = "never-review" detector = gl.create_detector(name=name, query=query, pipeline_config=pipeline_config) @@ -929,7 +929,7 @@ def test_delete_detector(gl: Groundlight): gl.get_detector(detector.id) # Create another detector to test deletion by ID string and that an attached image query is deleted - name2 = f"Test delete detector 2 {datetime.utcnow()}" + name2 = generate_test_detector_name("Test delete detector 2") detector2 = gl.create_detector(name=name2, query=query, pipeline_config=pipeline_config) gl.submit_image_query(detector2, "test/assets/dog.jpeg") @@ -959,7 +959,7 @@ def test_create_detector_with_invalid_priming_group_id(gl: Groundlight): Note: PrimingGroup IDs are provided by Groundlight representatives. If you would like to use a priming_group_id, please reach out to your Groundlight representative. """ - name = f"Test invalid priming {datetime.utcnow()}" + name = generate_test_detector_name("Test invalid priming") query = "Is there a dog?" pipeline_config = "never-review" priming_group_id = "prgrp_nonexistent12345678901234567890" diff --git a/test/integration/test_groundlight_expensive.py b/test/integration/test_groundlight_expensive.py index a45888c4..1693c8a3 100644 --- a/test/integration/test_groundlight_expensive.py +++ b/test/integration/test_groundlight_expensive.py @@ -7,9 +7,9 @@ # pylint: disable=wildcard-import,unused-wildcard-import,redefined-outer-name,import-outside-toplevel import random import time -from datetime import datetime import pytest +from conftest import generate_test_detector_name from groundlight import Groundlight from groundlight.internalapi import iq_is_answered, iq_is_confident from groundlight.optional_imports import * @@ -31,7 +31,7 @@ def fixture_gl() -> Groundlight: @pytest.mark.skip(reason="This test requires a human labeler who does not need to be in the testing loop") def test_human_label(gl: Groundlight): - detector = gl.create_detector(name=f"Test {datetime.utcnow()}", query="Is there a dog?") + detector = gl.create_detector(name=generate_test_detector_name(), query="Is there a dog?") img_query = gl.submit_image_query( detector=detector.id, image="test/assets/dog.jpeg", wait=60, human_review="ALWAYS" ) @@ -61,7 +61,7 @@ def test_detector_improvement(gl: Groundlight): random.seed(2741) - name = f"Test test_detector_improvement {datetime.utcnow()}" # Need a unique name + name = generate_test_detector_name("Test test_detector_improvement") query = "Is there a dog?" detector = gl.create_detector(name=name, query=query) @@ -126,7 +126,7 @@ def test_ask_method_quality(gl: Groundlight, detector: Detector): # asks for some level of quality on how fast ask_ml is and that we will get a confident result from ask_confident fast_always_yes_iq = gl.ask_ml(detector=detector.id, image="test/assets/dog.jpeg", wait=0) assert iq_is_answered(fast_always_yes_iq) - name = f"Test {datetime.utcnow()}" # Need a unique name + name = generate_test_detector_name() query = "Is there a dog?" detector = gl.create_detector(name=name, query=query, confidence_threshold=0.8) fast_iq = gl.ask_ml(detector=detector.id, image="test/assets/dog.jpeg", wait=0) diff --git a/test/unit/test_cli.py b/test/unit/test_cli.py index 38cc676c..90c368f7 100644 --- a/test/unit/test_cli.py +++ b/test/unit/test_cli.py @@ -1,9 +1,10 @@ import os import re import subprocess -from datetime import datetime from unittest.mock import patch +from conftest import generate_test_detector_name + def test_whoami(): completed_process = subprocess.run( @@ -25,7 +26,7 @@ def test_list_detector(): def test_detector_and_image_queries(): # test creating a detector - test_detector_name = f"testdetector {datetime.utcnow()}" + test_detector_name = generate_test_detector_name("testdetector") completed_process = subprocess.run( [ "groundlight", diff --git a/test/unit/test_detector_reset.py b/test/unit/test_detector_reset.py index 67b88514..3be962ce 100644 --- a/test/unit/test_detector_reset.py +++ b/test/unit/test_detector_reset.py @@ -1,7 +1,7 @@ import time -from datetime import datetime import pytest +from conftest import generate_test_detector_name from groundlight import ExperimentalApi from groundlight_openapi_client.exceptions import NotFoundException @@ -9,7 +9,7 @@ @pytest.mark.skip(reason="This is an expensive test, reset may take some time") def test_reset_retry(gl_experimental: ExperimentalApi): # Reset the detector, retrying in case the reset is still ongoing - det = gl_experimental.create_detector(f"Test {datetime.utcnow()}", "test_query") + det = gl_experimental.create_detector(generate_test_detector_name(), "test_query") iq = gl_experimental.submit_image_query(det, "test/assets/cat.jpeg") gl_experimental.reset_detector(det.id) success = False @@ -30,7 +30,7 @@ def test_reset_retry(gl_experimental: ExperimentalApi): def test_reset_training(gl_experimental: ExperimentalApi): # If we reset a detector, we should have low confidence after the reset low_confidence_threshold = 0.6 - det = gl_experimental.create_detector(f"Test {datetime.utcnow()}", "is this a cat") + det = gl_experimental.create_detector(generate_test_detector_name(), "is this a cat") gl_experimental.reset_detector(det.id) iq = gl_experimental.submit_image_query(det, "test/assets/cat.jpeg", human_review="NEVER") assert iq.result.confidence < low_confidence_threshold diff --git a/test/unit/test_experimental.py b/test/unit/test_experimental.py index 2b8f55f7..bcb67a56 100644 --- a/test/unit/test_experimental.py +++ b/test/unit/test_experimental.py @@ -1,7 +1,7 @@ import time -from datetime import datetime, timezone import pytest +from conftest import generate_test_detector_name from groundlight import ExperimentalApi from model import Detector, ImageQuery @@ -10,7 +10,7 @@ def test_detector_groups(gl_experimental: ExperimentalApi): """ verify that we can create a detector group and retrieve it """ - name = f"Test {datetime.utcnow()}" + name = generate_test_detector_name() created_group = gl_experimental.create_detector_group(name) all_groups = gl_experimental.list_detector_groups() assert created_group in all_groups @@ -34,7 +34,7 @@ def test_update_detector_name(gl_experimental: ExperimentalApi, detector: Detect """ verify that we can update the name of a detector """ - new_name = f"Test {datetime.utcnow()}" + new_name = generate_test_detector_name() gl_experimental.update_detector_name(detector.id, new_name) updated_detector = gl_experimental.get_detector(detector.id) assert updated_detector.name == new_name @@ -44,7 +44,7 @@ def test_update_detector_status(gl_experimental: ExperimentalApi): """ verify that we can update the status of a detector """ - detector = gl_experimental.get_or_create_detector(f"test {datetime.utcnow()}", "Is there a dog?") + detector = gl_experimental.get_or_create_detector(generate_test_detector_name(), "Is there a dog?") gl_experimental.update_detector_status(detector.id, False) updated_detector = gl_experimental.get_detector(detector.id) assert updated_detector.status.value == "OFF" @@ -57,7 +57,7 @@ def test_update_detector_escalation_type(gl_experimental: ExperimentalApi): """ verify that we can update the escalation type of a detector """ - detector = gl_experimental.get_or_create_detector(f"test {datetime.utcnow()}", "Is there a dog?") + detector = gl_experimental.get_or_create_detector(generate_test_detector_name(), "Is there a dog?") gl_experimental.update_detector_escalation_type(detector.id, "NO_HUMAN_LABELING") updated_detector = gl_experimental.get_detector(detector.id) updated_detector.escalation_type == "NO_HUMAN_LABELING" @@ -94,7 +94,7 @@ def test_text_recognition_detector(gl_experimental: ExperimentalApi): """ verify that we can create and submit to a text recognition detector """ - name = f"Test {datetime.utcnow()}" + name = generate_test_detector_name() created_detector = gl_experimental.create_text_recognition_detector( name, "What is the date and time?", confidence_threshold=0.0 ) @@ -107,7 +107,7 @@ def test_bounding_box_detector(gl_experimental: ExperimentalApi): """ Verify that we can create and submit to a bounding box detector """ - name = f"Test {datetime.now(timezone.utc)}" + name = generate_test_detector_name() created_detector = gl_experimental.create_bounding_box_detector( name, "Draw a bounding box around each dog in the image", "dog", confidence_threshold=0.0 ) @@ -121,7 +121,7 @@ def test_bounding_box_detector_async(gl_experimental: ExperimentalApi): """ Verify that we can create and submit to a bounding box detector with ask_async """ - name = f"Test {datetime.now(timezone.utc)}" + name = generate_test_detector_name() created_detector = gl_experimental.create_bounding_box_detector( name, "Draw a bounding box around each dog in the image", "dog", confidence_threshold=0.0 ) diff --git a/test/unit/test_http_retries.py b/test/unit/test_http_retries.py index 106c871f..e60f12c2 100644 --- a/test/unit/test_http_retries.py +++ b/test/unit/test_http_retries.py @@ -1,16 +1,15 @@ -from datetime import datetime from typing import Any, Callable from unittest import mock -from uuid import uuid4 import pytest +from conftest import generate_test_detector_name from groundlight import Groundlight from groundlight.binary_labels import Label from groundlight.internalapi import InternalApiError from model import Detector DEFAULT_CONFIDENCE_THRESHOLD = 0.9 -DETECTOR_NAME = f"test detector_{datetime.utcnow().strftime('%Y=%m-%d %H:%M:%S')}_{uuid4().hex[:8]}" +DETECTOR_NAME = generate_test_detector_name() TOTAL_RETRIES = 3 STATUS_CODES = range(500, 505) IMAGE_FILE = "test/assets/dog.jpeg" From 4b1ac5f532afcd7eb6aff6db1a4828c84b11b3c4 Mon Sep 17 00:00:00 2001 From: Tim Huff Date: Wed, 18 Mar 2026 21:43:55 +0000 Subject: [PATCH 3/9] trying a fixture-based approach --- test/conftest.py | 14 +++++--- test/integration/test_groundlight.py | 35 +++++++++---------- .../integration/test_groundlight_expensive.py | 8 ++--- test/unit/test_cli.py | 5 ++- test/unit/test_detector_reset.py | 6 ++-- test/unit/test_experimental.py | 16 ++++----- test/unit/test_http_retries.py | 27 +++++++------- 7 files changed, 59 insertions(+), 52 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 0bfe06e0..c36287ce 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -6,11 +6,17 @@ from model import Detector, ImageQuery, ImageQueryTypeEnum, ResultTypeEnum -def generate_test_detector_name(prefix: str = "Test") -> str: - """Generates a unique detector name with a timestamp and random suffix to avoid collisions in parallel CI.""" +def _generate_test_detector_name(prefix: str = "Test") -> str: + """Generates a detector name with a timestamp and random suffix to ensure uniqueness.""" return f"{prefix} {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')}_{uuid4().hex[:8]}" +@pytest.fixture(name="generate_test_detector_name") +def fixture_test_detector_name(): + """Fixture that provides a callable to generate unique detector names.""" + return _generate_test_detector_name + + def pytest_configure(config): # pylint: disable=unused-argument # Run environment check before tests gl = Groundlight() @@ -33,7 +39,7 @@ def fixture_detector(gl: Groundlight) -> Detector: """Creates a new Test detector.""" query = "Is there a dog?" pipeline_config = "never-review" - return gl.create_detector(name=generate_test_detector_name(), query=query, pipeline_config=pipeline_config) + return gl.create_detector(name=_generate_test_detector_name(), query=query, pipeline_config=pipeline_config) @pytest.fixture(name="count_detector") @@ -42,7 +48,7 @@ def fixture_count_detector(gl_experimental: ExperimentalApi) -> Detector: query = "How many dogs?" pipeline_config = "never-review-multi" # always predicts 0 return gl_experimental.create_counting_detector( - name=generate_test_detector_name(), query=query, class_name="dog", pipeline_config=pipeline_config + name=_generate_test_detector_name(), query=query, class_name="dog", pipeline_config=pipeline_config ) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 4cbd7f7d..ad88f9ad 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -5,10 +5,9 @@ import random import string import time -from typing import Any, Dict, Optional, Union +from typing import Any, Callable, Dict, Optional, Union import pytest -from conftest import generate_test_detector_name from groundlight import Groundlight from groundlight.binary_labels import VALID_DISPLAY_LABELS, Label, convert_internal_label_to_display from groundlight.internalapi import ApiException, InternalApiError, NotFoundError @@ -96,7 +95,7 @@ def test_create_groundlight_with_retries(): assert gl.api_client.configuration.retries.total == retries.total -def test_create_detector(gl: Groundlight): +def test_create_detector(gl: Groundlight, generate_test_detector_name: Callable): name = generate_test_detector_name() query = "Is there a dog?" _detector = gl.create_detector(name=name, query=query) @@ -117,7 +116,7 @@ def test_create_detector(gl: Groundlight): assert str(multiclass_detector) -def test_create_detector_with_pipeline_config(gl: Groundlight): +def test_create_detector_with_pipeline_config(gl: Groundlight, generate_test_detector_name: Callable): # "never-review" is a special model that always returns the same result with 100% confidence. # It's useful for testing. name = generate_test_detector_name("Test never-review") @@ -128,7 +127,7 @@ def test_create_detector_with_pipeline_config(gl: Groundlight): assert isinstance(_detector, Detector) -def test_create_detector_with_edge_pipeline_config(gl: Groundlight): +def test_create_detector_with_edge_pipeline_config(gl: Groundlight, generate_test_detector_name: Callable): name = generate_test_detector_name("Test edge-pipeline-config") query = "Is there a dog (edge-config)?" _detector = gl.create_detector( @@ -141,7 +140,7 @@ def test_create_detector_with_edge_pipeline_config(gl: Groundlight): assert isinstance(_detector, Detector) -def test_create_detector_with_confidence_threshold(gl: Groundlight): +def test_create_detector_with_confidence_threshold(gl: Groundlight, generate_test_detector_name: Callable): # "never-review" is a special model that always returns the same result with 100% confidence. # It's useful for testing. name = generate_test_detector_name("Test with confidence") @@ -197,7 +196,7 @@ def test_create_detector_with_confidence_threshold(gl: Groundlight): @pytest.mark.skip_for_edge_endpoint(reason="The edge-endpoint does not support passing detector metadata.") -def test_create_detector_with_everything(gl: Groundlight): +def test_create_detector_with_everything(gl: Groundlight, generate_test_detector_name: Callable): name = generate_test_detector_name() query = "Is there a dog?" group_name = "Test group" @@ -232,7 +231,7 @@ def test_list_detectors(gl: Groundlight): assert isinstance(detectors, PaginatedDetectorList) -def test_get_or_create_detector(gl: Groundlight): +def test_get_or_create_detector(gl: Groundlight, generate_test_detector_name: Callable): # With a unique name, we should be creating a new detector. unique_name = generate_test_detector_name() query = "Is there a dog?" @@ -410,7 +409,7 @@ def test_submit_image_query_with_low_request_timeout(gl: Groundlight, detector: @pytest.mark.skip_for_edge_endpoint(reason="The edge-endpoint does not support passing detector metadata.") -def test_create_detector_with_metadata(gl: Groundlight): +def test_create_detector_with_metadata(gl: Groundlight, generate_test_detector_name: Callable): name = generate_test_detector_name() query = "Is there a dog?" metadata = generate_random_dict(target_size_bytes=200) @@ -422,7 +421,7 @@ def test_create_detector_with_metadata(gl: Groundlight): @pytest.mark.skip_for_edge_endpoint(reason="The edge-endpoint does not support passing detector metadata.") -def test_get_or_create_detector_with_metadata(gl: Groundlight): +def test_get_or_create_detector_with_metadata(gl: Groundlight, generate_test_detector_name: Callable): unique_name = generate_test_detector_name() query = "Is there a dog?" metadata = generate_random_dict(target_size_bytes=200) @@ -443,7 +442,7 @@ def test_get_or_create_detector_with_metadata(gl: Groundlight): [""], ], ) -def test_create_detector_with_invalid_metadata(gl: Groundlight, metadata_list: Any): +def test_create_detector_with_invalid_metadata(gl: Groundlight, metadata_list: Any, generate_test_detector_name: Callable): name = generate_test_detector_name() query = "Is there a dog?" @@ -627,7 +626,7 @@ def test_list_image_queries(gl: Groundlight): assert is_valid_display_result(image_query.result) -def test_list_image_queries_with_filter(gl: Groundlight): +def test_list_image_queries_with_filter(gl: Groundlight, generate_test_detector_name: Callable): # We want a fresh detector so we know exactly what image queries are associated with it detector = gl.create_detector(name=generate_test_detector_name(), query="Is there a dog?") image_query_yes = gl.ask_async(detector=detector.id, image="test/assets/dog.jpeg", human_review="NEVER") @@ -855,7 +854,7 @@ def test_submit_image_query_with_empty_inspection_id(gl: Groundlight, detector: ) -def test_binary_detector(gl: Groundlight): +def test_binary_detector(gl: Groundlight, generate_test_detector_name: Callable): """ verify that we can create and submit to a binary detector """ @@ -866,7 +865,7 @@ def test_binary_detector(gl: Groundlight): assert binary_iq.result.label is not None -def test_counting_detector(gl: Groundlight): +def test_counting_detector(gl: Groundlight, generate_test_detector_name: Callable): """ verify that we can create and submit to a counting detector """ @@ -877,7 +876,7 @@ def test_counting_detector(gl: Groundlight): assert count_iq.result.count is not None -def test_counting_detector_async(gl: Groundlight): +def test_counting_detector_async(gl: Groundlight, generate_test_detector_name: Callable): """ verify that we can create and submit to a counting detector """ @@ -896,7 +895,7 @@ def test_counting_detector_async(gl: Groundlight): assert _image_query.result is not None -def test_multiclass_detector(gl: Groundlight): +def test_multiclass_detector(gl: Groundlight, generate_test_detector_name: Callable): """ verify that we can create and submit to a multi-class detector """ @@ -911,7 +910,7 @@ def test_multiclass_detector(gl: Groundlight): assert mc_iq.result.label in class_names -def test_delete_detector(gl: Groundlight): +def test_delete_detector(gl: Groundlight, generate_test_detector_name: Callable): """ Test deleting a detector by both ID and object, and verify proper error handling. """ @@ -952,7 +951,7 @@ def test_delete_detector(gl: Groundlight): gl.delete_detector(fake_detector_id) # type: ignore -def test_create_detector_with_invalid_priming_group_id(gl: Groundlight): +def test_create_detector_with_invalid_priming_group_id(gl: Groundlight, generate_test_detector_name: Callable): """ Test that creating a detector with a non-existent priming_group_id returns an appropriate error. diff --git a/test/integration/test_groundlight_expensive.py b/test/integration/test_groundlight_expensive.py index 1693c8a3..bd25450c 100644 --- a/test/integration/test_groundlight_expensive.py +++ b/test/integration/test_groundlight_expensive.py @@ -7,9 +7,9 @@ # pylint: disable=wildcard-import,unused-wildcard-import,redefined-outer-name,import-outside-toplevel import random import time +from typing import Callable import pytest -from conftest import generate_test_detector_name from groundlight import Groundlight from groundlight.internalapi import iq_is_answered, iq_is_confident from groundlight.optional_imports import * @@ -30,7 +30,7 @@ def fixture_gl() -> Groundlight: @pytest.mark.skip(reason="This test requires a human labeler who does not need to be in the testing loop") -def test_human_label(gl: Groundlight): +def test_human_label(gl: Groundlight, generate_test_detector_name: Callable): detector = gl.create_detector(name=generate_test_detector_name(), query="Is there a dog?") img_query = gl.submit_image_query( detector=detector.id, image="test/assets/dog.jpeg", wait=60, human_review="ALWAYS" @@ -52,7 +52,7 @@ def test_human_label(gl: Groundlight): @pytest.mark.skip(reason="This test can block development depending on the state of the service") @pytest.mark.skipif(MISSING_PIL, reason="Needs pillow") # type: ignore -def test_detector_improvement(gl: Groundlight): +def test_detector_improvement(gl: Groundlight, generate_test_detector_name: Callable): # test that we get confidence improvement after sending images in # Pass two of each type of image in import time @@ -122,7 +122,7 @@ def submit_noisy_image(image, label=None): @pytest.mark.skip( reason="We don't yet have an SLA level to test ask_confident against, and the test is flakey as a result" ) -def test_ask_method_quality(gl: Groundlight, detector: Detector): +def test_ask_method_quality(gl: Groundlight, detector: Detector, generate_test_detector_name: Callable): # asks for some level of quality on how fast ask_ml is and that we will get a confident result from ask_confident fast_always_yes_iq = gl.ask_ml(detector=detector.id, image="test/assets/dog.jpeg", wait=0) assert iq_is_answered(fast_always_yes_iq) diff --git a/test/unit/test_cli.py b/test/unit/test_cli.py index 90c368f7..b9335dc1 100644 --- a/test/unit/test_cli.py +++ b/test/unit/test_cli.py @@ -1,10 +1,9 @@ import os import re import subprocess +from typing import Callable from unittest.mock import patch -from conftest import generate_test_detector_name - def test_whoami(): completed_process = subprocess.run( @@ -24,7 +23,7 @@ def test_list_detector(): assert completed_process.returncode == 0 -def test_detector_and_image_queries(): +def test_detector_and_image_queries(generate_test_detector_name: Callable): # test creating a detector test_detector_name = generate_test_detector_name("testdetector") completed_process = subprocess.run( diff --git a/test/unit/test_detector_reset.py b/test/unit/test_detector_reset.py index 3be962ce..bf41fe5f 100644 --- a/test/unit/test_detector_reset.py +++ b/test/unit/test_detector_reset.py @@ -1,13 +1,13 @@ import time +from typing import Callable import pytest -from conftest import generate_test_detector_name from groundlight import ExperimentalApi from groundlight_openapi_client.exceptions import NotFoundException @pytest.mark.skip(reason="This is an expensive test, reset may take some time") -def test_reset_retry(gl_experimental: ExperimentalApi): +def test_reset_retry(gl_experimental: ExperimentalApi, generate_test_detector_name: Callable): # Reset the detector, retrying in case the reset is still ongoing det = gl_experimental.create_detector(generate_test_detector_name(), "test_query") iq = gl_experimental.submit_image_query(det, "test/assets/cat.jpeg") @@ -27,7 +27,7 @@ def test_reset_retry(gl_experimental: ExperimentalApi): @pytest.mark.skip(reason="This test does not work with strong 0 shot models, enabled by default based on your account") -def test_reset_training(gl_experimental: ExperimentalApi): +def test_reset_training(gl_experimental: ExperimentalApi, generate_test_detector_name: Callable): # If we reset a detector, we should have low confidence after the reset low_confidence_threshold = 0.6 det = gl_experimental.create_detector(generate_test_detector_name(), "is this a cat") diff --git a/test/unit/test_experimental.py b/test/unit/test_experimental.py index bcb67a56..c78b74cc 100644 --- a/test/unit/test_experimental.py +++ b/test/unit/test_experimental.py @@ -1,12 +1,12 @@ import time +from typing import Callable import pytest -from conftest import generate_test_detector_name from groundlight import ExperimentalApi from model import Detector, ImageQuery -def test_detector_groups(gl_experimental: ExperimentalApi): +def test_detector_groups(gl_experimental: ExperimentalApi, generate_test_detector_name: Callable): """ verify that we can create a detector group and retrieve it """ @@ -30,7 +30,7 @@ def test_update_detector_confidence_threshold(gl_experimental: ExperimentalApi, assert updated_detector.confidence_threshold == newer_confidence -def test_update_detector_name(gl_experimental: ExperimentalApi, detector: Detector): +def test_update_detector_name(gl_experimental: ExperimentalApi, detector: Detector, generate_test_detector_name: Callable): """ verify that we can update the name of a detector """ @@ -40,7 +40,7 @@ def test_update_detector_name(gl_experimental: ExperimentalApi, detector: Detect assert updated_detector.name == new_name -def test_update_detector_status(gl_experimental: ExperimentalApi): +def test_update_detector_status(gl_experimental: ExperimentalApi, generate_test_detector_name: Callable): """ verify that we can update the status of a detector """ @@ -53,7 +53,7 @@ def test_update_detector_status(gl_experimental: ExperimentalApi): assert updated_detector.status.value == "ON" -def test_update_detector_escalation_type(gl_experimental: ExperimentalApi): +def test_update_detector_escalation_type(gl_experimental: ExperimentalApi, generate_test_detector_name: Callable): """ verify that we can update the escalation type of a detector """ @@ -90,7 +90,7 @@ def test_submit_multiple_rois(gl_experimental: ExperimentalApi, image_query_one: gl_experimental.add_label(image_query_one, 3, [roi] * 3) -def test_text_recognition_detector(gl_experimental: ExperimentalApi): +def test_text_recognition_detector(gl_experimental: ExperimentalApi, generate_test_detector_name: Callable): """ verify that we can create and submit to a text recognition detector """ @@ -103,7 +103,7 @@ def test_text_recognition_detector(gl_experimental: ExperimentalApi): assert mc_iq.result.text is not None -def test_bounding_box_detector(gl_experimental: ExperimentalApi): +def test_bounding_box_detector(gl_experimental: ExperimentalApi, generate_test_detector_name: Callable): """ Verify that we can create and submit to a bounding box detector """ @@ -117,7 +117,7 @@ def test_bounding_box_detector(gl_experimental: ExperimentalApi): assert bbox_iq.rois is not None -def test_bounding_box_detector_async(gl_experimental: ExperimentalApi): +def test_bounding_box_detector_async(gl_experimental: ExperimentalApi, generate_test_detector_name: Callable): """ Verify that we can create and submit to a bounding box detector with ask_async """ diff --git a/test/unit/test_http_retries.py b/test/unit/test_http_retries.py index e60f12c2..694c20e7 100644 --- a/test/unit/test_http_retries.py +++ b/test/unit/test_http_retries.py @@ -2,14 +2,12 @@ from unittest import mock import pytest -from conftest import generate_test_detector_name from groundlight import Groundlight from groundlight.binary_labels import Label from groundlight.internalapi import InternalApiError from model import Detector DEFAULT_CONFIDENCE_THRESHOLD = 0.9 -DETECTOR_NAME = generate_test_detector_name() TOTAL_RETRIES = 3 STATUS_CODES = range(500, 505) IMAGE_FILE = "test/assets/dog.jpeg" @@ -22,30 +20,35 @@ def groundlight_fixture() -> Groundlight: return gl +@pytest.fixture(name="detector_name") +def detector_name_fixture(generate_test_detector_name) -> str: + return generate_test_detector_name("test detector") + + @pytest.fixture(name="detector") -def detector_fixture(gl: Groundlight) -> Detector: +def detector_fixture(gl: Groundlight, detector_name: str) -> Detector: return gl.get_or_create_detector( - name=DETECTOR_NAME, query="Is there a dog?", confidence_threshold=DEFAULT_CONFIDENCE_THRESHOLD + name=detector_name, query="Is there a dog?", confidence_threshold=DEFAULT_CONFIDENCE_THRESHOLD ) -def test_create_detector_attempts_retries(gl: Groundlight): +def test_create_detector_attempts_retries(gl: Groundlight, detector_name: str): run_test( mocked_call="urllib3.PoolManager.request", api_method=gl.create_detector, expected_call_counts=TOTAL_RETRIES + 1, - name=DETECTOR_NAME, + name=detector_name, query="Is there a dog?", confidence_threshold=DEFAULT_CONFIDENCE_THRESHOLD, ) -def test_get_or_create_detector_attempts_retries(gl: Groundlight): +def test_get_or_create_detector_attempts_retries(gl: Groundlight, detector_name: str): run_test( mocked_call="requests.request", api_method=gl.get_or_create_detector, expected_call_counts=TOTAL_RETRIES + 1, - name=DETECTOR_NAME, + name=detector_name, query="Is there a dog?", confidence_threshold=DEFAULT_CONFIDENCE_THRESHOLD, ) @@ -60,12 +63,12 @@ def test_get_detector_attempts_retries(gl: Groundlight, detector: Detector): ) -def test_get_detector_by_name_attempts_retries(gl: Groundlight): +def test_get_detector_by_name_attempts_retries(gl: Groundlight, detector_name: str): run_test( mocked_call="requests.request", api_method=gl.get_detector_by_name, expected_call_counts=TOTAL_RETRIES + 1, - name=DETECTOR_NAME, + name=detector_name, ) @@ -75,12 +78,12 @@ def test_list_detectors_attempts_retries(gl: Groundlight): ) -def test_submit_image_query_attempts_retries(gl: Groundlight): +def test_submit_image_query_attempts_retries(gl: Groundlight, detector_name: str): run_test( mocked_call="urllib3.PoolManager.request", api_method=gl.submit_image_query, expected_call_counts=TOTAL_RETRIES + 1, - detector=DETECTOR_NAME, + detector=detector_name, image=IMAGE_FILE, wait=1, ) From a2cae395490af3b118aa8ba2e72a698281ef01b4 Mon Sep 17 00:00:00 2001 From: Auto-format Bot Date: Wed, 18 Mar 2026 21:44:34 +0000 Subject: [PATCH 4/9] Automatically reformatting code --- test/integration/test_groundlight.py | 4 +++- test/unit/test_experimental.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index ad88f9ad..7d6dd6ee 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -442,7 +442,9 @@ def test_get_or_create_detector_with_metadata(gl: Groundlight, generate_test_det [""], ], ) -def test_create_detector_with_invalid_metadata(gl: Groundlight, metadata_list: Any, generate_test_detector_name: Callable): +def test_create_detector_with_invalid_metadata( + gl: Groundlight, metadata_list: Any, generate_test_detector_name: Callable +): name = generate_test_detector_name() query = "Is there a dog?" diff --git a/test/unit/test_experimental.py b/test/unit/test_experimental.py index c78b74cc..312c4394 100644 --- a/test/unit/test_experimental.py +++ b/test/unit/test_experimental.py @@ -30,7 +30,9 @@ def test_update_detector_confidence_threshold(gl_experimental: ExperimentalApi, assert updated_detector.confidence_threshold == newer_confidence -def test_update_detector_name(gl_experimental: ExperimentalApi, detector: Detector, generate_test_detector_name: Callable): +def test_update_detector_name( + gl_experimental: ExperimentalApi, detector: Detector, generate_test_detector_name: Callable +): """ verify that we can update the name of a detector """ From 9781a0a4b891690d2909a9157f1f6e093c4d625f Mon Sep 17 00:00:00 2001 From: Tim Huff Date: Wed, 18 Mar 2026 21:53:29 +0000 Subject: [PATCH 5/9] renaming fixture --- test/conftest.py | 12 ++-- test/integration/test_groundlight.py | 70 +++++++++---------- .../integration/test_groundlight_expensive.py | 12 ++-- test/unit/test_cli.py | 4 +- test/unit/test_detector_reset.py | 8 +-- test/unit/test_experimental.py | 28 ++++---- test/unit/test_http_retries.py | 26 +++---- 7 files changed, 80 insertions(+), 80 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index c36287ce..408b55e6 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -6,15 +6,15 @@ from model import Detector, ImageQuery, ImageQueryTypeEnum, ResultTypeEnum -def _generate_test_detector_name(prefix: str = "Test") -> str: +def _generate_unique_detector_name(prefix: str = "Test") -> str: """Generates a detector name with a timestamp and random suffix to ensure uniqueness.""" return f"{prefix} {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')}_{uuid4().hex[:8]}" -@pytest.fixture(name="generate_test_detector_name") -def fixture_test_detector_name(): +@pytest.fixture(name="detector_name") +def fixture_detector_name(): """Fixture that provides a callable to generate unique detector names.""" - return _generate_test_detector_name + return _generate_unique_detector_name def pytest_configure(config): # pylint: disable=unused-argument @@ -39,7 +39,7 @@ def fixture_detector(gl: Groundlight) -> Detector: """Creates a new Test detector.""" query = "Is there a dog?" pipeline_config = "never-review" - return gl.create_detector(name=_generate_test_detector_name(), query=query, pipeline_config=pipeline_config) + return gl.create_detector(name=_generate_unique_detector_name(), query=query, pipeline_config=pipeline_config) @pytest.fixture(name="count_detector") @@ -48,7 +48,7 @@ def fixture_count_detector(gl_experimental: ExperimentalApi) -> Detector: query = "How many dogs?" pipeline_config = "never-review-multi" # always predicts 0 return gl_experimental.create_counting_detector( - name=_generate_test_detector_name(), query=query, class_name="dog", pipeline_config=pipeline_config + name=_generate_unique_detector_name(), query=query, class_name="dog", pipeline_config=pipeline_config ) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index ad88f9ad..1567f7c5 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -95,8 +95,8 @@ def test_create_groundlight_with_retries(): assert gl.api_client.configuration.retries.total == retries.total -def test_create_detector(gl: Groundlight, generate_test_detector_name: Callable): - name = generate_test_detector_name() +def test_create_detector(gl: Groundlight, detector_name: Callable): + name = detector_name() query = "Is there a dog?" _detector = gl.create_detector(name=name, query=query) assert str(_detector) @@ -107,19 +107,19 @@ def test_create_detector(gl: Groundlight, generate_test_detector_name: Callable) # Test creating dectors with other modes count_detector = gl.create_detector( - name=generate_test_detector_name(), query=query, mode=ModeEnum.COUNT, class_names="dog" + name=detector_name(), query=query, mode=ModeEnum.COUNT, class_names="dog" ) assert str(count_detector) multiclass_detector = gl.create_detector( - name=generate_test_detector_name(), query=query, mode=ModeEnum.MULTI_CLASS, class_names=["dog", "cat"] + name=detector_name(), query=query, mode=ModeEnum.MULTI_CLASS, class_names=["dog", "cat"] ) assert str(multiclass_detector) -def test_create_detector_with_pipeline_config(gl: Groundlight, generate_test_detector_name: Callable): +def test_create_detector_with_pipeline_config(gl: Groundlight, detector_name: Callable): # "never-review" is a special model that always returns the same result with 100% confidence. # It's useful for testing. - name = generate_test_detector_name("Test never-review") + name = detector_name("Test never-review") query = "Is there a dog (always-pass)?" pipeline_config = "never-review" _detector = gl.create_detector(name=name, query=query, pipeline_config=pipeline_config) @@ -127,8 +127,8 @@ def test_create_detector_with_pipeline_config(gl: Groundlight, generate_test_det assert isinstance(_detector, Detector) -def test_create_detector_with_edge_pipeline_config(gl: Groundlight, generate_test_detector_name: Callable): - name = generate_test_detector_name("Test edge-pipeline-config") +def test_create_detector_with_edge_pipeline_config(gl: Groundlight, detector_name: Callable): + name = detector_name("Test edge-pipeline-config") query = "Is there a dog (edge-config)?" _detector = gl.create_detector( name=name, @@ -140,10 +140,10 @@ def test_create_detector_with_edge_pipeline_config(gl: Groundlight, generate_tes assert isinstance(_detector, Detector) -def test_create_detector_with_confidence_threshold(gl: Groundlight, generate_test_detector_name: Callable): +def test_create_detector_with_confidence_threshold(gl: Groundlight, detector_name: Callable): # "never-review" is a special model that always returns the same result with 100% confidence. # It's useful for testing. - name = generate_test_detector_name("Test with confidence") + name = detector_name("Test with confidence") query = "Is there a dog in the image?" pipeline_config = "never-review" confidence_threshold = 0.825 @@ -196,8 +196,8 @@ def test_create_detector_with_confidence_threshold(gl: Groundlight, generate_tes @pytest.mark.skip_for_edge_endpoint(reason="The edge-endpoint does not support passing detector metadata.") -def test_create_detector_with_everything(gl: Groundlight, generate_test_detector_name: Callable): - name = generate_test_detector_name() +def test_create_detector_with_everything(gl: Groundlight, detector_name: Callable): + name = detector_name() query = "Is there a dog?" group_name = "Test group" confidence_threshold = 0.825 @@ -231,9 +231,9 @@ def test_list_detectors(gl: Groundlight): assert isinstance(detectors, PaginatedDetectorList) -def test_get_or_create_detector(gl: Groundlight, generate_test_detector_name: Callable): +def test_get_or_create_detector(gl: Groundlight, detector_name: Callable): # With a unique name, we should be creating a new detector. - unique_name = generate_test_detector_name() + unique_name = detector_name() query = "Is there a dog?" detector = gl.get_or_create_detector(name=unique_name, query=query) assert str(detector) @@ -409,8 +409,8 @@ def test_submit_image_query_with_low_request_timeout(gl: Groundlight, detector: @pytest.mark.skip_for_edge_endpoint(reason="The edge-endpoint does not support passing detector metadata.") -def test_create_detector_with_metadata(gl: Groundlight, generate_test_detector_name: Callable): - name = generate_test_detector_name() +def test_create_detector_with_metadata(gl: Groundlight, detector_name: Callable): + name = detector_name() query = "Is there a dog?" metadata = generate_random_dict(target_size_bytes=200) detector = gl.create_detector(name=name, query=query, metadata=metadata) @@ -421,8 +421,8 @@ def test_create_detector_with_metadata(gl: Groundlight, generate_test_detector_n @pytest.mark.skip_for_edge_endpoint(reason="The edge-endpoint does not support passing detector metadata.") -def test_get_or_create_detector_with_metadata(gl: Groundlight, generate_test_detector_name: Callable): - unique_name = generate_test_detector_name() +def test_get_or_create_detector_with_metadata(gl: Groundlight, detector_name: Callable): + unique_name = detector_name() query = "Is there a dog?" metadata = generate_random_dict(target_size_bytes=200) detector = gl.get_or_create_detector(name=unique_name, query=query, metadata=metadata) @@ -442,8 +442,8 @@ def test_get_or_create_detector_with_metadata(gl: Groundlight, generate_test_det [""], ], ) -def test_create_detector_with_invalid_metadata(gl: Groundlight, metadata_list: Any, generate_test_detector_name: Callable): - name = generate_test_detector_name() +def test_create_detector_with_invalid_metadata(gl: Groundlight, metadata_list: Any, detector_name: Callable): + name = detector_name() query = "Is there a dog?" for metadata in metadata_list: @@ -626,9 +626,9 @@ def test_list_image_queries(gl: Groundlight): assert is_valid_display_result(image_query.result) -def test_list_image_queries_with_filter(gl: Groundlight, generate_test_detector_name: Callable): +def test_list_image_queries_with_filter(gl: Groundlight, detector_name: Callable): # We want a fresh detector so we know exactly what image queries are associated with it - detector = gl.create_detector(name=generate_test_detector_name(), query="Is there a dog?") + detector = gl.create_detector(name=detector_name(), query="Is there a dog?") image_query_yes = gl.ask_async(detector=detector.id, image="test/assets/dog.jpeg", human_review="NEVER") image_query_no = gl.ask_async(detector=detector.id, image="test/assets/cat.jpeg", human_review="NEVER") iq_ids = [image_query_yes.id, image_query_no.id] @@ -854,33 +854,33 @@ def test_submit_image_query_with_empty_inspection_id(gl: Groundlight, detector: ) -def test_binary_detector(gl: Groundlight, generate_test_detector_name: Callable): +def test_binary_detector(gl: Groundlight, detector_name: Callable): """ verify that we can create and submit to a binary detector """ - name = generate_test_detector_name() + name = detector_name() created_detector = gl.create_binary_detector(name, "Is there a dog", confidence_threshold=0.0) assert created_detector is not None binary_iq = gl.submit_image_query(created_detector, "test/assets/dog.jpeg") assert binary_iq.result.label is not None -def test_counting_detector(gl: Groundlight, generate_test_detector_name: Callable): +def test_counting_detector(gl: Groundlight, detector_name: Callable): """ verify that we can create and submit to a counting detector """ - name = generate_test_detector_name() + name = detector_name() created_detector = gl.create_counting_detector(name, "How many dogs", "dog", confidence_threshold=0.0) assert created_detector is not None count_iq = gl.submit_image_query(created_detector, "test/assets/dog.jpeg") assert count_iq.result.count is not None -def test_counting_detector_async(gl: Groundlight, generate_test_detector_name: Callable): +def test_counting_detector_async(gl: Groundlight, detector_name: Callable): """ verify that we can create and submit to a counting detector """ - name = generate_test_detector_name() + name = detector_name() created_detector = gl.create_counting_detector(name, "How many dogs", "dog", confidence_threshold=0.0) assert created_detector is not None async_iq = gl.ask_async(created_detector, "test/assets/dog.jpeg") @@ -895,11 +895,11 @@ def test_counting_detector_async(gl: Groundlight, generate_test_detector_name: C assert _image_query.result is not None -def test_multiclass_detector(gl: Groundlight, generate_test_detector_name: Callable): +def test_multiclass_detector(gl: Groundlight, detector_name: Callable): """ verify that we can create and submit to a multi-class detector """ - name = generate_test_detector_name() + name = detector_name() class_names = ["Golden Retriever", "Labrador Retriever", "Poodle"] created_detector = gl.create_multiclass_detector( name, "What kind of dog is this?", class_names=class_names, confidence_threshold=0.0 @@ -910,12 +910,12 @@ def test_multiclass_detector(gl: Groundlight, generate_test_detector_name: Calla assert mc_iq.result.label in class_names -def test_delete_detector(gl: Groundlight, generate_test_detector_name: Callable): +def test_delete_detector(gl: Groundlight, detector_name: Callable): """ Test deleting a detector by both ID and object, and verify proper error handling. """ # Create a detector to delete - name = generate_test_detector_name("Test delete detector") + name = detector_name("Test delete detector") query = "Is there a dog to delete?" pipeline_config = "never-review" detector = gl.create_detector(name=name, query=query, pipeline_config=pipeline_config) @@ -928,7 +928,7 @@ def test_delete_detector(gl: Groundlight, generate_test_detector_name: Callable) gl.get_detector(detector.id) # Create another detector to test deletion by ID string and that an attached image query is deleted - name2 = generate_test_detector_name("Test delete detector 2") + name2 = detector_name("Test delete detector 2") detector2 = gl.create_detector(name=name2, query=query, pipeline_config=pipeline_config) gl.submit_image_query(detector2, "test/assets/dog.jpeg") @@ -951,14 +951,14 @@ def test_delete_detector(gl: Groundlight, generate_test_detector_name: Callable) gl.delete_detector(fake_detector_id) # type: ignore -def test_create_detector_with_invalid_priming_group_id(gl: Groundlight, generate_test_detector_name: Callable): +def test_create_detector_with_invalid_priming_group_id(gl: Groundlight, detector_name: Callable): """ Test that creating a detector with a non-existent priming_group_id returns an appropriate error. Note: PrimingGroup IDs are provided by Groundlight representatives. If you would like to use a priming_group_id, please reach out to your Groundlight representative. """ - name = generate_test_detector_name("Test invalid priming") + name = detector_name("Test invalid priming") query = "Is there a dog?" pipeline_config = "never-review" priming_group_id = "prgrp_nonexistent12345678901234567890" diff --git a/test/integration/test_groundlight_expensive.py b/test/integration/test_groundlight_expensive.py index bd25450c..27529ac2 100644 --- a/test/integration/test_groundlight_expensive.py +++ b/test/integration/test_groundlight_expensive.py @@ -30,8 +30,8 @@ def fixture_gl() -> Groundlight: @pytest.mark.skip(reason="This test requires a human labeler who does not need to be in the testing loop") -def test_human_label(gl: Groundlight, generate_test_detector_name: Callable): - detector = gl.create_detector(name=generate_test_detector_name(), query="Is there a dog?") +def test_human_label(gl: Groundlight, detector_name: Callable): + detector = gl.create_detector(name=detector_name(), query="Is there a dog?") img_query = gl.submit_image_query( detector=detector.id, image="test/assets/dog.jpeg", wait=60, human_review="ALWAYS" ) @@ -52,7 +52,7 @@ def test_human_label(gl: Groundlight, generate_test_detector_name: Callable): @pytest.mark.skip(reason="This test can block development depending on the state of the service") @pytest.mark.skipif(MISSING_PIL, reason="Needs pillow") # type: ignore -def test_detector_improvement(gl: Groundlight, generate_test_detector_name: Callable): +def test_detector_improvement(gl: Groundlight, detector_name: Callable): # test that we get confidence improvement after sending images in # Pass two of each type of image in import time @@ -61,7 +61,7 @@ def test_detector_improvement(gl: Groundlight, generate_test_detector_name: Call random.seed(2741) - name = generate_test_detector_name("Test test_detector_improvement") + name = detector_name("Test test_detector_improvement") query = "Is there a dog?" detector = gl.create_detector(name=name, query=query) @@ -122,11 +122,11 @@ def submit_noisy_image(image, label=None): @pytest.mark.skip( reason="We don't yet have an SLA level to test ask_confident against, and the test is flakey as a result" ) -def test_ask_method_quality(gl: Groundlight, detector: Detector, generate_test_detector_name: Callable): +def test_ask_method_quality(gl: Groundlight, detector: Detector, detector_name: Callable): # asks for some level of quality on how fast ask_ml is and that we will get a confident result from ask_confident fast_always_yes_iq = gl.ask_ml(detector=detector.id, image="test/assets/dog.jpeg", wait=0) assert iq_is_answered(fast_always_yes_iq) - name = generate_test_detector_name() + name = detector_name() query = "Is there a dog?" detector = gl.create_detector(name=name, query=query, confidence_threshold=0.8) fast_iq = gl.ask_ml(detector=detector.id, image="test/assets/dog.jpeg", wait=0) diff --git a/test/unit/test_cli.py b/test/unit/test_cli.py index b9335dc1..609fd8d6 100644 --- a/test/unit/test_cli.py +++ b/test/unit/test_cli.py @@ -23,9 +23,9 @@ def test_list_detector(): assert completed_process.returncode == 0 -def test_detector_and_image_queries(generate_test_detector_name: Callable): +def test_detector_and_image_queries(detector_name: Callable): # test creating a detector - test_detector_name = generate_test_detector_name("testdetector") + test_detector_name = detector_name("testdetector") completed_process = subprocess.run( [ "groundlight", diff --git a/test/unit/test_detector_reset.py b/test/unit/test_detector_reset.py index bf41fe5f..ccead3b7 100644 --- a/test/unit/test_detector_reset.py +++ b/test/unit/test_detector_reset.py @@ -7,9 +7,9 @@ @pytest.mark.skip(reason="This is an expensive test, reset may take some time") -def test_reset_retry(gl_experimental: ExperimentalApi, generate_test_detector_name: Callable): +def test_reset_retry(gl_experimental: ExperimentalApi, detector_name: Callable): # Reset the detector, retrying in case the reset is still ongoing - det = gl_experimental.create_detector(generate_test_detector_name(), "test_query") + det = gl_experimental.create_detector(detector_name(), "test_query") iq = gl_experimental.submit_image_query(det, "test/assets/cat.jpeg") gl_experimental.reset_detector(det.id) success = False @@ -27,10 +27,10 @@ def test_reset_retry(gl_experimental: ExperimentalApi, generate_test_detector_na @pytest.mark.skip(reason="This test does not work with strong 0 shot models, enabled by default based on your account") -def test_reset_training(gl_experimental: ExperimentalApi, generate_test_detector_name: Callable): +def test_reset_training(gl_experimental: ExperimentalApi, detector_name: Callable): # If we reset a detector, we should have low confidence after the reset low_confidence_threshold = 0.6 - det = gl_experimental.create_detector(generate_test_detector_name(), "is this a cat") + det = gl_experimental.create_detector(detector_name(), "is this a cat") gl_experimental.reset_detector(det.id) iq = gl_experimental.submit_image_query(det, "test/assets/cat.jpeg", human_review="NEVER") assert iq.result.confidence < low_confidence_threshold diff --git a/test/unit/test_experimental.py b/test/unit/test_experimental.py index c78b74cc..9c2237e5 100644 --- a/test/unit/test_experimental.py +++ b/test/unit/test_experimental.py @@ -6,11 +6,11 @@ from model import Detector, ImageQuery -def test_detector_groups(gl_experimental: ExperimentalApi, generate_test_detector_name: Callable): +def test_detector_groups(gl_experimental: ExperimentalApi, detector_name: Callable): """ verify that we can create a detector group and retrieve it """ - name = generate_test_detector_name() + name = detector_name() created_group = gl_experimental.create_detector_group(name) all_groups = gl_experimental.list_detector_groups() assert created_group in all_groups @@ -30,21 +30,21 @@ def test_update_detector_confidence_threshold(gl_experimental: ExperimentalApi, assert updated_detector.confidence_threshold == newer_confidence -def test_update_detector_name(gl_experimental: ExperimentalApi, detector: Detector, generate_test_detector_name: Callable): +def test_update_detector_name(gl_experimental: ExperimentalApi, detector: Detector, detector_name: Callable): """ verify that we can update the name of a detector """ - new_name = generate_test_detector_name() + new_name = detector_name() gl_experimental.update_detector_name(detector.id, new_name) updated_detector = gl_experimental.get_detector(detector.id) assert updated_detector.name == new_name -def test_update_detector_status(gl_experimental: ExperimentalApi, generate_test_detector_name: Callable): +def test_update_detector_status(gl_experimental: ExperimentalApi, detector_name: Callable): """ verify that we can update the status of a detector """ - detector = gl_experimental.get_or_create_detector(generate_test_detector_name(), "Is there a dog?") + detector = gl_experimental.get_or_create_detector(detector_name(), "Is there a dog?") gl_experimental.update_detector_status(detector.id, False) updated_detector = gl_experimental.get_detector(detector.id) assert updated_detector.status.value == "OFF" @@ -53,11 +53,11 @@ def test_update_detector_status(gl_experimental: ExperimentalApi, generate_test_ assert updated_detector.status.value == "ON" -def test_update_detector_escalation_type(gl_experimental: ExperimentalApi, generate_test_detector_name: Callable): +def test_update_detector_escalation_type(gl_experimental: ExperimentalApi, detector_name: Callable): """ verify that we can update the escalation type of a detector """ - detector = gl_experimental.get_or_create_detector(generate_test_detector_name(), "Is there a dog?") + detector = gl_experimental.get_or_create_detector(detector_name(), "Is there a dog?") gl_experimental.update_detector_escalation_type(detector.id, "NO_HUMAN_LABELING") updated_detector = gl_experimental.get_detector(detector.id) updated_detector.escalation_type == "NO_HUMAN_LABELING" @@ -90,11 +90,11 @@ def test_submit_multiple_rois(gl_experimental: ExperimentalApi, image_query_one: gl_experimental.add_label(image_query_one, 3, [roi] * 3) -def test_text_recognition_detector(gl_experimental: ExperimentalApi, generate_test_detector_name: Callable): +def test_text_recognition_detector(gl_experimental: ExperimentalApi, detector_name: Callable): """ verify that we can create and submit to a text recognition detector """ - name = generate_test_detector_name() + name = detector_name() created_detector = gl_experimental.create_text_recognition_detector( name, "What is the date and time?", confidence_threshold=0.0 ) @@ -103,11 +103,11 @@ def test_text_recognition_detector(gl_experimental: ExperimentalApi, generate_te assert mc_iq.result.text is not None -def test_bounding_box_detector(gl_experimental: ExperimentalApi, generate_test_detector_name: Callable): +def test_bounding_box_detector(gl_experimental: ExperimentalApi, detector_name: Callable): """ Verify that we can create and submit to a bounding box detector """ - name = generate_test_detector_name() + name = detector_name() created_detector = gl_experimental.create_bounding_box_detector( name, "Draw a bounding box around each dog in the image", "dog", confidence_threshold=0.0 ) @@ -117,11 +117,11 @@ def test_bounding_box_detector(gl_experimental: ExperimentalApi, generate_test_d assert bbox_iq.rois is not None -def test_bounding_box_detector_async(gl_experimental: ExperimentalApi, generate_test_detector_name: Callable): +def test_bounding_box_detector_async(gl_experimental: ExperimentalApi, detector_name: Callable): """ Verify that we can create and submit to a bounding box detector with ask_async """ - name = generate_test_detector_name() + name = detector_name() created_detector = gl_experimental.create_bounding_box_detector( name, "Draw a bounding box around each dog in the image", "dog", confidence_threshold=0.0 ) diff --git a/test/unit/test_http_retries.py b/test/unit/test_http_retries.py index 694c20e7..c838af74 100644 --- a/test/unit/test_http_retries.py +++ b/test/unit/test_http_retries.py @@ -20,35 +20,35 @@ def groundlight_fixture() -> Groundlight: return gl -@pytest.fixture(name="detector_name") -def detector_name_fixture(generate_test_detector_name) -> str: - return generate_test_detector_name("test detector") +@pytest.fixture(name="retry_detector_name") +def retry_detector_name_fixture(detector_name) -> str: + return detector_name("test detector") @pytest.fixture(name="detector") -def detector_fixture(gl: Groundlight, detector_name: str) -> Detector: +def detector_fixture(gl: Groundlight, retry_detector_name: str) -> Detector: return gl.get_or_create_detector( - name=detector_name, query="Is there a dog?", confidence_threshold=DEFAULT_CONFIDENCE_THRESHOLD + name=retry_detector_name, query="Is there a dog?", confidence_threshold=DEFAULT_CONFIDENCE_THRESHOLD ) -def test_create_detector_attempts_retries(gl: Groundlight, detector_name: str): +def test_create_detector_attempts_retries(gl: Groundlight, retry_detector_name: str): run_test( mocked_call="urllib3.PoolManager.request", api_method=gl.create_detector, expected_call_counts=TOTAL_RETRIES + 1, - name=detector_name, + name=retry_detector_name, query="Is there a dog?", confidence_threshold=DEFAULT_CONFIDENCE_THRESHOLD, ) -def test_get_or_create_detector_attempts_retries(gl: Groundlight, detector_name: str): +def test_get_or_create_detector_attempts_retries(gl: Groundlight, retry_detector_name: str): run_test( mocked_call="requests.request", api_method=gl.get_or_create_detector, expected_call_counts=TOTAL_RETRIES + 1, - name=detector_name, + name=retry_detector_name, query="Is there a dog?", confidence_threshold=DEFAULT_CONFIDENCE_THRESHOLD, ) @@ -63,12 +63,12 @@ def test_get_detector_attempts_retries(gl: Groundlight, detector: Detector): ) -def test_get_detector_by_name_attempts_retries(gl: Groundlight, detector_name: str): +def test_get_detector_by_name_attempts_retries(gl: Groundlight, retry_detector_name: str): run_test( mocked_call="requests.request", api_method=gl.get_detector_by_name, expected_call_counts=TOTAL_RETRIES + 1, - name=detector_name, + name=retry_detector_name, ) @@ -78,12 +78,12 @@ def test_list_detectors_attempts_retries(gl: Groundlight): ) -def test_submit_image_query_attempts_retries(gl: Groundlight, detector_name: str): +def test_submit_image_query_attempts_retries(gl: Groundlight, retry_detector_name: str): run_test( mocked_call="urllib3.PoolManager.request", api_method=gl.submit_image_query, expected_call_counts=TOTAL_RETRIES + 1, - detector=detector_name, + detector=retry_detector_name, image=IMAGE_FILE, wait=1, ) From e60fad9bbdaca351ebcda9cd385d07b00dd66bf4 Mon Sep 17 00:00:00 2001 From: Auto-format Bot Date: Wed, 18 Mar 2026 21:55:14 +0000 Subject: [PATCH 6/9] Automatically reformatting code --- test/integration/test_groundlight.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 1567f7c5..6d69bb63 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -106,9 +106,7 @@ def test_create_detector(gl: Groundlight, detector_name: Callable): ), "We expected the default confidence threshold to be used." # Test creating dectors with other modes - count_detector = gl.create_detector( - name=detector_name(), query=query, mode=ModeEnum.COUNT, class_names="dog" - ) + count_detector = gl.create_detector(name=detector_name(), query=query, mode=ModeEnum.COUNT, class_names="dog") assert str(count_detector) multiclass_detector = gl.create_detector( name=detector_name(), query=query, mode=ModeEnum.MULTI_CLASS, class_names=["dog", "cat"] From 3c5b58683548ed73affee57422270c04d3e408f4 Mon Sep 17 00:00:00 2001 From: Tim Huff Date: Wed, 18 Mar 2026 22:01:20 +0000 Subject: [PATCH 7/9] code cleanup --- test/unit/test_http_retries.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/test/unit/test_http_retries.py b/test/unit/test_http_retries.py index c838af74..04b75393 100644 --- a/test/unit/test_http_retries.py +++ b/test/unit/test_http_retries.py @@ -20,35 +20,30 @@ def groundlight_fixture() -> Groundlight: return gl -@pytest.fixture(name="retry_detector_name") -def retry_detector_name_fixture(detector_name) -> str: - return detector_name("test detector") - - @pytest.fixture(name="detector") -def detector_fixture(gl: Groundlight, retry_detector_name: str) -> Detector: +def detector_fixture(gl: Groundlight, detector_name: Callable) -> Detector: return gl.get_or_create_detector( - name=retry_detector_name, query="Is there a dog?", confidence_threshold=DEFAULT_CONFIDENCE_THRESHOLD + name=detector_name(), query="Is there a dog?", confidence_threshold=DEFAULT_CONFIDENCE_THRESHOLD ) -def test_create_detector_attempts_retries(gl: Groundlight, retry_detector_name: str): +def test_create_detector_attempts_retries(gl: Groundlight, detector_name: Callable): run_test( mocked_call="urllib3.PoolManager.request", api_method=gl.create_detector, expected_call_counts=TOTAL_RETRIES + 1, - name=retry_detector_name, + name=detector_name(), query="Is there a dog?", confidence_threshold=DEFAULT_CONFIDENCE_THRESHOLD, ) -def test_get_or_create_detector_attempts_retries(gl: Groundlight, retry_detector_name: str): +def test_get_or_create_detector_attempts_retries(gl: Groundlight, detector_name: Callable): run_test( mocked_call="requests.request", api_method=gl.get_or_create_detector, expected_call_counts=TOTAL_RETRIES + 1, - name=retry_detector_name, + name=detector_name(), query="Is there a dog?", confidence_threshold=DEFAULT_CONFIDENCE_THRESHOLD, ) @@ -63,12 +58,12 @@ def test_get_detector_attempts_retries(gl: Groundlight, detector: Detector): ) -def test_get_detector_by_name_attempts_retries(gl: Groundlight, retry_detector_name: str): +def test_get_detector_by_name_attempts_retries(gl: Groundlight, detector_name: Callable): run_test( mocked_call="requests.request", api_method=gl.get_detector_by_name, expected_call_counts=TOTAL_RETRIES + 1, - name=retry_detector_name, + name=detector_name(), ) @@ -78,12 +73,12 @@ def test_list_detectors_attempts_retries(gl: Groundlight): ) -def test_submit_image_query_attempts_retries(gl: Groundlight, retry_detector_name: str): +def test_submit_image_query_attempts_retries(gl: Groundlight, detector_name: Callable): run_test( mocked_call="urllib3.PoolManager.request", api_method=gl.submit_image_query, expected_call_counts=TOTAL_RETRIES + 1, - detector=retry_detector_name, + detector=detector_name(), image=IMAGE_FILE, wait=1, ) From bb836e7b7a22a9af2c36c82cf7ea919c5f78087e Mon Sep 17 00:00:00 2001 From: Tim Huff Date: Wed, 18 Mar 2026 22:08:02 +0000 Subject: [PATCH 8/9] adding type hint --- test/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/conftest.py b/test/conftest.py index 408b55e6..a3160457 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,4 +1,5 @@ from datetime import datetime +from typing import Callable from uuid import uuid4 import pytest @@ -12,7 +13,7 @@ def _generate_unique_detector_name(prefix: str = "Test") -> str: @pytest.fixture(name="detector_name") -def fixture_detector_name(): +def fixture_detector_name() -> Callable[..., str]: """Fixture that provides a callable to generate unique detector names.""" return _generate_unique_detector_name From 8375191557e7afd8c5594fd85fa4f24fd79102ac Mon Sep 17 00:00:00 2001 From: Tim Huff Date: Wed, 18 Mar 2026 22:18:34 +0000 Subject: [PATCH 9/9] fixing linter error --- test/unit/test_cli.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/unit/test_cli.py b/test/unit/test_cli.py index 609fd8d6..47911b70 100644 --- a/test/unit/test_cli.py +++ b/test/unit/test_cli.py @@ -41,7 +41,9 @@ def test_detector_and_image_queries(detector_name: Callable): check=False, ) assert completed_process.returncode == 0 - det_id_on_create = re.search("id='([^']+)'", completed_process.stdout).group(1) + match = re.search("id='([^']+)'", completed_process.stdout) + assert match is not None + det_id_on_create = match.group(1) # The output of the create-detector command looks something like: # id='det_abc123' # type= @@ -59,7 +61,9 @@ def test_detector_and_image_queries(detector_name: Callable): check=False, ) assert completed_process.returncode == 0 - det_id_on_get = re.search("id='([^']+)'", completed_process.stdout).group(1) + match = re.search("id='([^']+)'", completed_process.stdout) + assert match is not None + det_id_on_get = match.group(1) assert det_id_on_create == det_id_on_get completed_process = subprocess.run( ["groundlight", "get-detector", det_id_on_create],