Skip to content

Commit 7ed2b6d

Browse files
committed
Address PR feedback: fix typo, update docstrings, migrate parse_obj, add pagination, move tests
- Fix pg/pgp typo in test_get_priming_group_unknown_raises - Update priming_group_id docstrings to reference self-service create_priming_group - Replace deprecated parse_obj with model_validate across client, experimental_api, internalapi - Add page/page_size params to list_detector_pipelines and list_priming_groups, returning paginated types - Move test_priming_groups.py from test/unit to test/integration (tests hit real API)
1 parent 548c487 commit 7ed2b6d

File tree

5 files changed

+47
-47
lines changed

5 files changed

+47
-47
lines changed

src/groundlight/client.py

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ def get_detector(
304304
obj = self.detectors_api.get_detector(id=id, _request_timeout=request_timeout)
305305
except NotFoundException as e:
306306
raise NotFoundError(f"Detector with id '{id}' not found") from e
307-
return Detector.parse_obj(obj.to_dict())
307+
return Detector.model_validate(obj.to_dict())
308308

309309
def get_detector_by_name(self, name: str) -> Detector:
310310
"""
@@ -345,7 +345,7 @@ def list_detectors(self, page: int = 1, page_size: int = 10) -> PaginatedDetecto
345345
obj = self.detectors_api.list_detectors(
346346
page=page, page_size=page_size, _request_timeout=DEFAULT_REQUEST_TIMEOUT
347347
)
348-
return PaginatedDetectorList.parse_obj(obj.to_dict())
348+
return PaginatedDetectorList.model_validate(obj.to_dict())
349349

350350
def _prep_create_detector( # noqa: PLR0913 # pylint: disable=too-many-arguments, too-many-locals
351351
self,
@@ -457,8 +457,7 @@ def create_detector( # noqa: PLR0913
457457
:param class_names: The name or names of the class to use for the detector. Only used for multi-class
458458
and counting detectors.
459459
:param priming_group_id: Optional ID of an existing PrimingGroup to associate with this detector.
460-
PrimingGroup IDs are provided by Groundlight representatives. If you would like
461-
to use a priming_group_id, please reach out to your Groundlight representative.
460+
You can create a PrimingGroup using the ExperimentalApi's create_priming_group method.
462461
463462
:return: The created Detector object
464463
"""
@@ -631,7 +630,7 @@ def get_image_query(self, id: str) -> ImageQuery: # pylint: disable=redefined-b
631630
if obj.result_type == "counting" and getattr(obj.result, "label", None):
632631
obj.result.pop("label")
633632
obj.result["count"] = None
634-
iq = ImageQuery.parse_obj(obj.to_dict())
633+
iq = ImageQuery.model_validate(obj.to_dict())
635634
return self._fixup_image_query(iq)
636635

637636
def list_image_queries(
@@ -662,7 +661,7 @@ def list_image_queries(
662661
if detector_id:
663662
params["detector_id"] = detector_id
664663
obj = self.image_queries_api.list_image_queries(**params)
665-
image_queries = PaginatedImageQueryList.parse_obj(obj.to_dict())
664+
image_queries = PaginatedImageQueryList.model_validate(obj.to_dict())
666665
if image_queries.results is not None:
667666
image_queries.results = [self._fixup_image_query(iq) for iq in image_queries.results]
668667
return image_queries
@@ -835,7 +834,7 @@ def submit_image_query( # noqa: PLR0913 # pylint: disable=too-many-arguments, t
835834
params["image_query_id"] = image_query_id
836835

837836
raw_image_query = self.image_queries_api.submit_image_query(**params)
838-
image_query = ImageQuery.parse_obj(raw_image_query.to_dict())
837+
image_query = ImageQuery.model_validate(raw_image_query.to_dict())
839838

840839
if wait > 0:
841840
if confidence_threshold is None:
@@ -1626,8 +1625,7 @@ def create_counting_detector( # noqa: PLR0913 # pylint: disable=too-many-argume
16261625
information like location, purpose, or related system IDs. You can retrieve this
16271626
metadata later by calling `get_detector()`.
16281627
:param priming_group_id: Optional ID of an existing PrimingGroup to associate with this detector.
1629-
PrimingGroup IDs are provided by Groundlight representatives. If you would like
1630-
to use a priming_group_id, please reach out to your Groundlight representative.
1628+
You can create a PrimingGroup using the ExperimentalApi's create_priming_group method.
16311629
16321630
:return: The created Detector object
16331631
"""
@@ -1652,7 +1650,7 @@ def create_counting_detector( # noqa: PLR0913 # pylint: disable=too-many-argume
16521650

16531651
detector_creation_input.mode_configuration = mode_config
16541652
obj = self.detectors_api.create_detector(detector_creation_input, _request_timeout=DEFAULT_REQUEST_TIMEOUT)
1655-
return Detector.parse_obj(obj.to_dict())
1653+
return Detector.model_validate(obj.to_dict())
16561654

16571655
def create_binary_detector( # noqa: PLR0913 # pylint: disable=too-many-arguments, too-many-locals
16581656
self,
@@ -1697,7 +1695,7 @@ def create_binary_detector( # noqa: PLR0913 # pylint: disable=too-many-argument
16971695
priming_group_id=priming_group_id,
16981696
)
16991697
obj = self.detectors_api.create_detector(detector_creation_input, _request_timeout=DEFAULT_REQUEST_TIMEOUT)
1700-
return Detector.parse_obj(obj.to_dict())
1698+
return Detector.model_validate(obj.to_dict())
17011699

17021700
def create_multiclass_detector( # noqa: PLR0913 # pylint: disable=too-many-arguments, too-many-locals
17031701
self,
@@ -1749,8 +1747,7 @@ def create_multiclass_detector( # noqa: PLR0913 # pylint: disable=too-many-argu
17491747
information like location, purpose, or related system IDs. You can retrieve this
17501748
metadata later by calling `get_detector()`.
17511749
:param priming_group_id: Optional ID of an existing PrimingGroup to associate with this detector.
1752-
PrimingGroup IDs are provided by Groundlight representatives. If you would like
1753-
to use a priming_group_id, please reach out to your Groundlight representative.
1750+
You can create a PrimingGroup using the ExperimentalApi's create_priming_group method.
17541751
17551752
:return: The created Detector object
17561753
"""
@@ -1770,7 +1767,7 @@ def create_multiclass_detector( # noqa: PLR0913 # pylint: disable=too-many-argu
17701767
mode_config = MultiClassModeConfiguration(class_names=class_names)
17711768
detector_creation_input.mode_configuration = mode_config
17721769
obj = self.detectors_api.create_detector(detector_creation_input, _request_timeout=DEFAULT_REQUEST_TIMEOUT)
1773-
return Detector.parse_obj(obj.to_dict())
1770+
return Detector.model_validate(obj.to_dict())
17741771

17751772
def create_bounding_box_detector( # noqa: PLR0913 # pylint: disable=too-many-arguments, too-many-locals
17761773
self,
@@ -1829,8 +1826,7 @@ def create_bounding_box_detector( # noqa: PLR0913 # pylint: disable=too-many-ar
18291826
information like location, purpose, or related system IDs. You can retrieve this
18301827
metadata later by calling `get_detector()`.
18311828
:param priming_group_id: Optional ID of an existing PrimingGroup to associate with this detector.
1832-
PrimingGroup IDs are provided by Groundlight representatives. If you would like
1833-
to use a priming_group_id, please reach out to your Groundlight representative.
1829+
You can create a PrimingGroup using the ExperimentalApi's create_priming_group method.
18341830
18351831
:return: The created Detector object
18361832
"""
@@ -1855,4 +1851,4 @@ def create_bounding_box_detector( # noqa: PLR0913 # pylint: disable=too-many-ar
18551851

18561852
detector_creation_input.mode_configuration = mode_config
18571853
obj = self.detectors_api.create_detector(detector_creation_input, _request_timeout=DEFAULT_REQUEST_TIMEOUT)
1858-
return Detector.parse_obj(obj.to_dict())
1854+
return Detector.model_validate(obj.to_dict())

src/groundlight/experimental_api.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
EdgeModelInfo,
4040
MLPipeline,
4141
ModeEnum,
42+
PaginatedMLPipelineList,
43+
PaginatedPrimingGroupList,
4244
PaginatedRuleList,
4345
PayloadTemplate,
4446
PrimingGroup,
@@ -477,7 +479,7 @@ def list_rules(self, page=1, page_size=10) -> PaginatedRuleList:
477479
:return: PaginatedRuleList containing the rules and pagination info
478480
"""
479481
obj = self.actions_api.list_rules(page=page, page_size=page_size)
480-
return PaginatedRuleList.parse_obj(obj.to_dict())
482+
return PaginatedRuleList.model_validate(obj.to_dict())
481483

482484
def delete_all_rules(self, detector: Union[None, str, Detector] = None) -> int:
483485
"""
@@ -686,8 +688,7 @@ def create_text_recognition_detector( # noqa: PLR0913 # pylint: disable=too-man
686688
information like location, purpose, or related system IDs. You can retrieve this
687689
metadata later by calling `get_detector()`.
688690
:param priming_group_id: Optional ID of an existing PrimingGroup to associate with this detector.
689-
PrimingGroup IDs are provided by Groundlight representatives. If you would like
690-
to use a priming_group_id, please reach out to your Groundlight representative.
691+
You can create a PrimingGroup using the ExperimentalApi's create_priming_group method.
691692
692693
:return: The created Detector object
693694
"""
@@ -708,7 +709,7 @@ def create_text_recognition_detector( # noqa: PLR0913 # pylint: disable=too-man
708709

709710
detector_creation_input.mode_configuration = mode_config
710711
obj = self.detectors_api.create_detector(detector_creation_input, _request_timeout=DEFAULT_REQUEST_TIMEOUT)
711-
return Detector.parse_obj(obj.to_dict())
712+
return Detector.model_validate(obj.to_dict())
712713

713714
def _download_mlbinary_url(self, detector: Union[str, Detector]) -> EdgeModelInfo:
714715
"""
@@ -718,7 +719,7 @@ def _download_mlbinary_url(self, detector: Union[str, Detector]) -> EdgeModelInf
718719
if isinstance(detector, Detector):
719720
detector = detector.id
720721
obj = self._edge_model_download_api.get_model_urls(detector)
721-
return EdgeModelInfo.parse_obj(obj.to_dict())
722+
return EdgeModelInfo.model_validate(obj.to_dict())
722723

723724
def download_mlbinary(self, detector: Union[str, Detector], output_dir: str) -> None:
724725
"""
@@ -840,7 +841,9 @@ def edge_base_url(self) -> str:
840841
# ML Pipeline methods
841842
# ---------------------------------------------------------------------------
842843

843-
def list_detector_pipelines(self, detector: Union[str, Detector]) -> List[MLPipeline]:
844+
def list_detector_pipelines(
845+
self, detector: Union[str, Detector], page: int = 1, page_size: int = 10
846+
) -> PaginatedMLPipelineList:
844847
"""
845848
Lists all ML pipelines associated with a given detector.
846849
@@ -852,25 +855,27 @@ def list_detector_pipelines(self, detector: Union[str, Detector]) -> List[MLPipe
852855
gl = ExperimentalApi()
853856
detector = gl.get_detector("det_abc123")
854857
pipelines = gl.list_detector_pipelines(detector)
855-
for p in pipelines:
858+
for p in pipelines.results:
856859
if p.is_active_pipeline:
857860
print(f"Active pipeline: {p.id}, config={p.pipeline_config}")
858861
859862
:param detector: A Detector object or detector ID string.
860-
:return: A list of MLPipeline objects for this detector.
863+
:param page: The page number to retrieve (1-based indexing).
864+
:param page_size: The number of pipelines to return per page.
865+
:return: PaginatedMLPipelineList containing the requested page of pipelines and pagination metadata.
861866
"""
862867
detector_id = detector.id if isinstance(detector, Detector) else detector
863868
try:
864-
paginated_result = self.detectors_api.list_detector_pipelines(detector_id)
865-
return [MLPipeline(**p.to_dict()) for p in paginated_result.results]
869+
obj = self.detectors_api.list_detector_pipelines(detector_id, page=page, page_size=page_size)
870+
return PaginatedMLPipelineList.model_validate(obj.to_dict())
866871
except NotFoundException as e:
867872
raise NotFoundError(f"Detector '{detector_id}' not found.") from e
868873

869874
# ---------------------------------------------------------------------------
870875
# PrimingGroup methods
871876
# ---------------------------------------------------------------------------
872877

873-
def list_priming_groups(self) -> List[PrimingGroup]:
878+
def list_priming_groups(self, page: int = 1, page_size: int = 10) -> PaginatedPrimingGroupList:
874879
"""
875880
Lists all PrimingGroups owned by the authenticated user's account.
876881
@@ -881,13 +886,15 @@ def list_priming_groups(self) -> List[PrimingGroup]:
881886
882887
gl = ExperimentalApi()
883888
groups = gl.list_priming_groups()
884-
for g in groups:
889+
for g in groups.results:
885890
print(f"{g.name}: {g.id}")
886891
887-
:return: A list of PrimingGroup objects.
892+
:param page: The page number to retrieve (1-based indexing).
893+
:param page_size: The number of priming groups to return per page.
894+
:return: PaginatedPrimingGroupList containing the requested page of priming groups and pagination metadata.
888895
"""
889-
paginated_result = self.priming_groups_api.list_priming_groups()
890-
return [PrimingGroup.parse_obj(pg.to_dict()) for pg in paginated_result.results]
896+
obj = self.priming_groups_api.list_priming_groups(page=page, page_size=page_size)
897+
return PaginatedPrimingGroupList.model_validate(obj.to_dict())
891898

892899
def create_priming_group(
893900
self,
@@ -908,7 +915,7 @@ def create_priming_group(
908915
gl = ExperimentalApi()
909916
detector = gl.get_detector("det_abc123")
910917
pipelines = gl.list_detector_pipelines(detector)
911-
active = next(p for p in pipelines if p.is_active_pipeline)
918+
active = next(p for p in pipelines.results if p.is_active_pipeline)
912919
913920
priming_group = gl.create_priming_group(
914921
name="door-detector-primer",
@@ -933,7 +940,7 @@ def create_priming_group(
933940
disable_shadow_pipelines=disable_shadow_pipelines,
934941
)
935942
result = self.priming_groups_api.create_priming_group(request)
936-
return PrimingGroup.parse_obj(result.to_dict())
943+
return PrimingGroup.model_validate(result.to_dict())
937944

938945
def get_priming_group(self, priming_group_id: str) -> PrimingGroup:
939946
"""
@@ -950,7 +957,7 @@ def get_priming_group(self, priming_group_id: str) -> PrimingGroup:
950957
"""
951958
try:
952959
result = self.priming_groups_api.get_priming_group(priming_group_id)
953-
return PrimingGroup.parse_obj(result.to_dict())
960+
return PrimingGroup.model_validate(result.to_dict())
954961
except NotFoundException as e:
955962
raise NotFoundError(f"PrimingGroup '{priming_group_id}' not found.") from e
956963
except ApiException as e:

src/groundlight/internalapi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ def _get_detector_by_name(self, name: str) -> Detector:
248248
raise RuntimeError(
249249
f"We found multiple ({parsed['count']}) detectors with the same name. This shouldn't happen.",
250250
)
251-
return Detector.parse_obj(parsed["results"][0])
251+
return Detector.model_validate(parsed["results"][0])
252252

253253
@RequestsRetryDecorator()
254254
def start_inspection(self) -> str:

test/integration/test_groundlight.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -953,8 +953,7 @@ def test_create_detector_with_invalid_priming_group_id(gl: Groundlight, detector
953953
"""
954954
Test that creating a detector with a non-existent priming_group_id returns an appropriate error.
955955
956-
Note: PrimingGroup IDs are provided by Groundlight representatives. If you would like to
957-
use a priming_group_id, please reach out to your Groundlight representative.
956+
Note: You can create a PrimingGroup using the ExperimentalApi's create_priming_group method.
958957
"""
959958
name = detector_name("Test invalid priming")
960959
query = "Is there a dog?"
Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,15 @@
2323
def test_list_detector_pipelines_returns_list(gl_experimental: ExperimentalApi, detector):
2424
"""A freshly created detector has at least one pipeline."""
2525
pipelines = gl_experimental.list_detector_pipelines(detector)
26-
assert isinstance(pipelines, list)
27-
assert len(pipelines) >= 1
28-
assert all(isinstance(p, MLPipeline) for p in pipelines)
26+
assert len(pipelines.results) >= 1
27+
assert all(isinstance(p, MLPipeline) for p in pipelines.results)
2928

3029

3130
def test_list_detector_pipelines_accepts_detector_id_string(gl_experimental: ExperimentalApi, detector):
3231
"""list_detector_pipelines should accept a raw ID string as well as a Detector object."""
3332
by_obj = gl_experimental.list_detector_pipelines(detector)
3433
by_id = gl_experimental.list_detector_pipelines(detector.id)
35-
assert [p.id for p in by_obj] == [p.id for p in by_id]
34+
assert [p.id for p in by_obj.results] == [p.id for p in by_id.results]
3635

3736

3837
def test_list_detector_pipelines_unknown_detector_raises(gl_experimental: ExperimentalApi):
@@ -47,8 +46,7 @@ def test_list_detector_pipelines_unknown_detector_raises(gl_experimental: Experi
4746

4847
def test_list_priming_groups_returns_list(gl_experimental: ExperimentalApi):
4948
groups = gl_experimental.list_priming_groups()
50-
assert isinstance(groups, list)
51-
assert all(isinstance(g, PrimingGroup) for g in groups)
49+
assert all(isinstance(g, PrimingGroup) for g in groups.results)
5250

5351

5452
# ---------------------------------------------------------------------------
@@ -75,7 +73,7 @@ def _wait_for_trained_pipeline(gl_experimental: ExperimentalApi, detector, timeo
7573
deadline = time.monotonic() + timeout
7674
while time.monotonic() < deadline:
7775
pipelines = gl_experimental.list_detector_pipelines(detector)
78-
for p in pipelines:
76+
for p in pipelines.results:
7977
if p.is_active_pipeline and p.trained_at is not None:
8078
return p
8179
time.sleep(5)
@@ -137,7 +135,7 @@ def test_get_priming_group(gl_experimental: ExperimentalApi, detector):
137135
@pytest.mark.expensive
138136
def test_get_priming_group_unknown_raises(gl_experimental: ExperimentalApi):
139137
with pytest.raises(NotFoundError):
140-
gl_experimental.get_priming_group("pgp_doesnotexist000000000000")
138+
gl_experimental.get_priming_group("pg_doesnotexist000000000000")
141139

142140

143141
@pytest.mark.expensive
@@ -165,6 +163,6 @@ def test_created_priming_group_appears_in_list(gl_experimental: ExperimentalApi,
165163
)
166164

167165
groups = gl_experimental.list_priming_groups()
168-
assert any(g.id == pg.id for g in groups)
166+
assert any(g.id == pg.id for g in groups.results)
169167

170168
gl_experimental.delete_priming_group(pg.id)

0 commit comments

Comments
 (0)