Skip to content

Commit f1e3abb

Browse files
committed
refactor(priming-group): move pipeline and priming group methods to ExperimentalApi
1 parent dc83316 commit f1e3abb

File tree

2 files changed

+160
-157
lines changed

2 files changed

+160
-157
lines changed

src/groundlight/client.py

Lines changed: 0 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
Detector,
3535
DetectorGroup,
3636
ImageQuery,
37-
MLPipeline,
3837
ModeEnum,
3938
PaginatedDetectorList,
4039
PaginatedImageQueryList,
@@ -1856,159 +1855,3 @@ def create_bounding_box_detector( # noqa: PLR0913 # pylint: disable=too-many-ar
18561855
obj = self.detectors_api.create_detector(detector_creation_input, _request_timeout=DEFAULT_REQUEST_TIMEOUT)
18571856
return Detector.parse_obj(obj.to_dict())
18581857

1859-
# ---------------------------------------------------------------------------
1860-
# ML Pipeline methods
1861-
# ---------------------------------------------------------------------------
1862-
1863-
def list_detector_pipelines(self, detector: Union[str, Detector]) -> List[MLPipeline]:
1864-
"""
1865-
Lists all ML pipelines associated with a given detector.
1866-
1867-
Each detector can have multiple pipelines (active, edge, shadow, etc.). This method returns
1868-
all of them, which is useful when selecting a source pipeline to seed a new PrimingGroup.
1869-
1870-
**Example usage**::
1871-
1872-
gl = Groundlight()
1873-
detector = gl.get_detector("det_abc123")
1874-
pipelines = gl.list_detector_pipelines(detector)
1875-
for p in pipelines:
1876-
if p.is_active_pipeline:
1877-
print(f"Active pipeline: {p.id}, config={p.pipeline_config}")
1878-
1879-
:param detector: A Detector object or detector ID string.
1880-
:return: A list of MLPipeline objects for this detector.
1881-
"""
1882-
detector_id = detector.id if isinstance(detector, Detector) else detector
1883-
url = f"{self.api_client.configuration.host}/v1/detectors/{detector_id}/pipelines"
1884-
response = requests.get(
1885-
url, headers=self.api_client._headers(), verify=self.api_client.configuration.verify_ssl
1886-
)
1887-
if response.status_code == 404:
1888-
raise NotFoundError(f"Detector '{detector_id}' not found.")
1889-
response.raise_for_status()
1890-
data = response.json()
1891-
return [MLPipeline(**item) for item in data.get("results", [])]
1892-
1893-
# ---------------------------------------------------------------------------
1894-
# PrimingGroup methods
1895-
# ---------------------------------------------------------------------------
1896-
1897-
def list_priming_groups(self) -> List[PrimingGroup]:
1898-
"""
1899-
Lists all PrimingGroups owned by the authenticated user's account.
1900-
1901-
PrimingGroups let you seed new detectors with a pre-trained model so they start with a
1902-
meaningful head start instead of a blank slate.
1903-
1904-
**Example usage**::
1905-
1906-
gl = Groundlight()
1907-
groups = gl.list_priming_groups()
1908-
for g in groups:
1909-
print(f"{g.name}: {g.id}")
1910-
1911-
:return: A list of PrimingGroup objects.
1912-
"""
1913-
url = f"{self.api_client.configuration.host}/v1/priming-groups"
1914-
response = requests.get(
1915-
url, headers=self.api_client._headers(), verify=self.api_client.configuration.verify_ssl
1916-
)
1917-
response.raise_for_status()
1918-
data = response.json()
1919-
return [PrimingGroup(**item) for item in data.get("results", [])]
1920-
1921-
def create_priming_group(
1922-
self,
1923-
name: str,
1924-
source_ml_pipeline_id: str,
1925-
canonical_query: Optional[str] = None,
1926-
disable_shadow_pipelines: bool = False,
1927-
) -> PrimingGroup:
1928-
"""
1929-
Creates a new PrimingGroup seeded from an existing ML pipeline.
1930-
1931-
The trained model binary from the source pipeline is copied into the new PrimingGroup.
1932-
Detectors subsequently created with this PrimingGroup's ID will start with that model
1933-
already loaded, bypassing the cold-start period.
1934-
1935-
**Example usage**::
1936-
1937-
gl = Groundlight()
1938-
detector = gl.get_detector("det_abc123")
1939-
pipelines = gl.list_detector_pipelines(detector)
1940-
active = next(p for p in pipelines if p.is_active_pipeline)
1941-
1942-
priming_group = gl.create_priming_group(
1943-
name="door-detector-primer",
1944-
source_ml_pipeline_id=active.id,
1945-
canonical_query="Is the door open?",
1946-
disable_shadow_pipelines=True,
1947-
)
1948-
print(f"Created priming group: {priming_group.id}")
1949-
1950-
:param name: A short, descriptive name for the priming group.
1951-
:param source_ml_pipeline_id: The ID of an MLPipeline whose trained model will seed this group.
1952-
The pipeline must belong to a detector in your account.
1953-
:param canonical_query: An optional description of the visual question this group answers.
1954-
:param disable_shadow_pipelines: If True, detectors created in this group will not receive
1955-
default shadow pipelines, ensuring the primed model stays active.
1956-
:return: The created PrimingGroup object.
1957-
"""
1958-
url = f"{self.api_client.configuration.host}/v1/priming-groups"
1959-
payload: dict = {
1960-
"name": name,
1961-
"source_ml_pipeline_id": source_ml_pipeline_id,
1962-
"disable_shadow_pipelines": disable_shadow_pipelines,
1963-
}
1964-
if canonical_query is not None:
1965-
payload["canonical_query"] = canonical_query
1966-
response = requests.post(
1967-
url, json=payload, headers=self.api_client._headers(), verify=self.api_client.configuration.verify_ssl
1968-
)
1969-
response.raise_for_status()
1970-
return PrimingGroup(**response.json())
1971-
1972-
def get_priming_group(self, priming_group_id: str) -> PrimingGroup:
1973-
"""
1974-
Retrieves a PrimingGroup by ID.
1975-
1976-
**Example usage**::
1977-
1978-
gl = Groundlight()
1979-
pg = gl.get_priming_group("pgp_abc123")
1980-
print(f"Priming group name: {pg.name}")
1981-
1982-
:param priming_group_id: The ID of the PrimingGroup to retrieve.
1983-
:return: The PrimingGroup object.
1984-
"""
1985-
url = f"{self.api_client.configuration.host}/v1/priming-groups/{priming_group_id}"
1986-
response = requests.get(
1987-
url, headers=self.api_client._headers(), verify=self.api_client.configuration.verify_ssl
1988-
)
1989-
if response.status_code == 404:
1990-
raise NotFoundError(f"PrimingGroup '{priming_group_id}' not found.")
1991-
response.raise_for_status()
1992-
return PrimingGroup(**response.json())
1993-
1994-
def delete_priming_group(self, priming_group_id: str) -> None:
1995-
"""
1996-
Deletes (soft-deletes) a PrimingGroup owned by the authenticated user.
1997-
1998-
This does not delete any detectors that were created using this priming group —
1999-
it only removes the priming group itself. Detectors already created remain unaffected.
2000-
2001-
**Example usage**::
2002-
2003-
gl = Groundlight()
2004-
gl.delete_priming_group("pgp_abc123")
2005-
2006-
:param priming_group_id: The ID of the PrimingGroup to delete.
2007-
"""
2008-
url = f"{self.api_client.configuration.host}/v1/priming-groups/{priming_group_id}"
2009-
response = requests.delete(
2010-
url, headers=self.api_client._headers(), verify=self.api_client.configuration.verify_ssl
2011-
)
2012-
if response.status_code == 404:
2013-
raise NotFoundError(f"PrimingGroup '{priming_group_id}' not found.")
2014-
response.raise_for_status()

src/groundlight/experimental_api.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,17 @@
3232
Condition,
3333
Detector,
3434
EdgeModelInfo,
35+
MLPipeline,
3536
ModeEnum,
3637
PaginatedRuleList,
3738
PayloadTemplate,
39+
PrimingGroup,
3840
Rule,
3941
WebhookAction,
4042
)
4143
from urllib3.response import HTTPResponse
4244

45+
from groundlight.exceptions import NotFoundError
4346
from groundlight.images import parse_supported_image_types
4447
from groundlight.internalapi import _generate_request_id
4548
from groundlight.optional_imports import Image, np
@@ -817,3 +820,160 @@ def make_generic_api_request( # noqa: PLR0913 # pylint: disable=too-many-argume
817820
auth_settings=["ApiToken"],
818821
_preload_content=False, # This returns the urllib3 response rather than trying any type of processing
819822
)
823+
824+
# ---------------------------------------------------------------------------
825+
# ML Pipeline methods
826+
# ---------------------------------------------------------------------------
827+
828+
def list_detector_pipelines(self, detector: Union[str, Detector]) -> List[MLPipeline]:
829+
"""
830+
Lists all ML pipelines associated with a given detector.
831+
832+
Each detector can have multiple pipelines (active, edge, shadow, etc.). This method returns
833+
all of them, which is useful when selecting a source pipeline to seed a new PrimingGroup.
834+
835+
**Example usage**::
836+
837+
gl = ExperimentalApi()
838+
detector = gl.get_detector("det_abc123")
839+
pipelines = gl.list_detector_pipelines(detector)
840+
for p in pipelines:
841+
if p.is_active_pipeline:
842+
print(f"Active pipeline: {p.id}, config={p.pipeline_config}")
843+
844+
:param detector: A Detector object or detector ID string.
845+
:return: A list of MLPipeline objects for this detector.
846+
"""
847+
detector_id = detector.id if isinstance(detector, Detector) else detector
848+
url = f"{self.api_client.configuration.host}/v1/detectors/{detector_id}/pipelines"
849+
response = requests.get(
850+
url, headers=self.api_client._headers(), verify=self.api_client.configuration.verify_ssl
851+
)
852+
if response.status_code == 404:
853+
raise NotFoundError(f"Detector '{detector_id}' not found.")
854+
response.raise_for_status()
855+
data = response.json()
856+
return [MLPipeline(**item) for item in data.get("results", [])]
857+
858+
# ---------------------------------------------------------------------------
859+
# PrimingGroup methods
860+
# ---------------------------------------------------------------------------
861+
862+
def list_priming_groups(self) -> List[PrimingGroup]:
863+
"""
864+
Lists all PrimingGroups owned by the authenticated user's account.
865+
866+
PrimingGroups let you seed new detectors with a pre-trained model so they start with a
867+
meaningful head start instead of a blank slate.
868+
869+
**Example usage**::
870+
871+
gl = ExperimentalApi()
872+
groups = gl.list_priming_groups()
873+
for g in groups:
874+
print(f"{g.name}: {g.id}")
875+
876+
:return: A list of PrimingGroup objects.
877+
"""
878+
url = f"{self.api_client.configuration.host}/v1/priming-groups"
879+
response = requests.get(
880+
url, headers=self.api_client._headers(), verify=self.api_client.configuration.verify_ssl
881+
)
882+
response.raise_for_status()
883+
data = response.json()
884+
return [PrimingGroup(**item) for item in data.get("results", [])]
885+
886+
def create_priming_group(
887+
self,
888+
name: str,
889+
source_ml_pipeline_id: str,
890+
canonical_query: Optional[str] = None,
891+
disable_shadow_pipelines: bool = False,
892+
) -> PrimingGroup:
893+
"""
894+
Creates a new PrimingGroup seeded from an existing ML pipeline.
895+
896+
The trained model binary from the source pipeline is copied into the new PrimingGroup.
897+
Detectors subsequently created with this PrimingGroup's ID will start with that model
898+
already loaded, bypassing the cold-start period.
899+
900+
**Example usage**::
901+
902+
gl = ExperimentalApi()
903+
detector = gl.get_detector("det_abc123")
904+
pipelines = gl.list_detector_pipelines(detector)
905+
active = next(p for p in pipelines if p.is_active_pipeline)
906+
907+
priming_group = gl.create_priming_group(
908+
name="door-detector-primer",
909+
source_ml_pipeline_id=active.id,
910+
canonical_query="Is the door open?",
911+
disable_shadow_pipelines=True,
912+
)
913+
print(f"Created priming group: {priming_group.id}")
914+
915+
:param name: A short, descriptive name for the priming group.
916+
:param source_ml_pipeline_id: The ID of an MLPipeline whose trained model will seed this group.
917+
The pipeline must belong to a detector in your account.
918+
:param canonical_query: An optional description of the visual question this group answers.
919+
:param disable_shadow_pipelines: If True, detectors created in this group will not receive
920+
default shadow pipelines, ensuring the primed model stays active.
921+
:return: The created PrimingGroup object.
922+
"""
923+
url = f"{self.api_client.configuration.host}/v1/priming-groups"
924+
payload: dict = {
925+
"name": name,
926+
"source_ml_pipeline_id": source_ml_pipeline_id,
927+
"disable_shadow_pipelines": disable_shadow_pipelines,
928+
}
929+
if canonical_query is not None:
930+
payload["canonical_query"] = canonical_query
931+
response = requests.post(
932+
url, json=payload, headers=self.api_client._headers(), verify=self.api_client.configuration.verify_ssl
933+
)
934+
response.raise_for_status()
935+
return PrimingGroup(**response.json())
936+
937+
def get_priming_group(self, priming_group_id: str) -> PrimingGroup:
938+
"""
939+
Retrieves a PrimingGroup by ID.
940+
941+
**Example usage**::
942+
943+
gl = ExperimentalApi()
944+
pg = gl.get_priming_group("pgp_abc123")
945+
print(f"Priming group name: {pg.name}")
946+
947+
:param priming_group_id: The ID of the PrimingGroup to retrieve.
948+
:return: The PrimingGroup object.
949+
"""
950+
url = f"{self.api_client.configuration.host}/v1/priming-groups/{priming_group_id}"
951+
response = requests.get(
952+
url, headers=self.api_client._headers(), verify=self.api_client.configuration.verify_ssl
953+
)
954+
if response.status_code == 404:
955+
raise NotFoundError(f"PrimingGroup '{priming_group_id}' not found.")
956+
response.raise_for_status()
957+
return PrimingGroup(**response.json())
958+
959+
def delete_priming_group(self, priming_group_id: str) -> None:
960+
"""
961+
Deletes (soft-deletes) a PrimingGroup owned by the authenticated user.
962+
963+
This does not delete any detectors that were created using this priming group —
964+
it only removes the priming group itself. Detectors already created remain unaffected.
965+
966+
**Example usage**::
967+
968+
gl = ExperimentalApi()
969+
gl.delete_priming_group("pgp_abc123")
970+
971+
:param priming_group_id: The ID of the PrimingGroup to delete.
972+
"""
973+
url = f"{self.api_client.configuration.host}/v1/priming-groups/{priming_group_id}"
974+
response = requests.delete(
975+
url, headers=self.api_client._headers(), verify=self.api_client.configuration.verify_ssl
976+
)
977+
if response.status_code == 404:
978+
raise NotFoundError(f"PrimingGroup '{priming_group_id}' not found.")
979+
response.raise_for_status()

0 commit comments

Comments
 (0)