From bf3974152efa7ec507669c314620a6722c46d677 Mon Sep 17 00:00:00 2001 From: brandon Date: Mon, 9 Dec 2024 17:14:02 -0800 Subject: [PATCH 1/4] Allows for filtering by detector_id --- generated/docs/ImageQueriesApi.md | 4 +- .../api/image_queries_api.py | 5 + .../model/patched_detector_request.py | 151 +++++++++++------- generated/model.py | 2 +- spec/public-api.yaml | 7 +- src/groundlight/client.py | 11 +- test/integration/test_groundlight.py | 13 ++ 7 files changed, 128 insertions(+), 65 deletions(-) diff --git a/generated/docs/ImageQueriesApi.md b/generated/docs/ImageQueriesApi.md index 7f39a4da..47a65ae5 100644 --- a/generated/docs/ImageQueriesApi.md +++ b/generated/docs/ImageQueriesApi.md @@ -203,11 +203,12 @@ with groundlight_openapi_client.ApiClient(configuration) as api_client: api_instance = image_queries_api.ImageQueriesApi(api_client) page = 1 # int | A page number within the paginated result set. (optional) page_size = 1 # int | Number of items to return per page. (optional) + predictor_id = "predictor_id_example" # str | Optionally filter image queries by detector ID. (optional) # example passing only required values which don't have defaults set # and optional values try: - api_response = api_instance.list_image_queries(page=page, page_size=page_size) + api_response = api_instance.list_image_queries(page=page, page_size=page_size, predictor_id=predictor_id) pprint(api_response) except groundlight_openapi_client.ApiException as e: print("Exception when calling ImageQueriesApi->list_image_queries: %s\n" % e) @@ -220,6 +221,7 @@ Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **page** | **int**| A page number within the paginated result set. | [optional] **page_size** | **int**| Number of items to return per page. | [optional] + **predictor_id** | **str**| Optionally filter image queries by detector ID. | [optional] ### Return type diff --git a/generated/groundlight_openapi_client/api/image_queries_api.py b/generated/groundlight_openapi_client/api/image_queries_api.py index cd8d0577..b25ad772 100644 --- a/generated/groundlight_openapi_client/api/image_queries_api.py +++ b/generated/groundlight_openapi_client/api/image_queries_api.py @@ -129,6 +129,7 @@ def __init__(self, api_client=None): "all": [ "page", "page_size", + "predictor_id", ], "required": [], "nullable": [], @@ -141,14 +142,17 @@ def __init__(self, api_client=None): "openapi_types": { "page": (int,), "page_size": (int,), + "predictor_id": (str,), }, "attribute_map": { "page": "page", "page_size": "page_size", + "predictor_id": "predictor_id", }, "location_map": { "page": "query", "page_size": "query", + "predictor_id": "query", }, "collection_format_map": {}, }, @@ -375,6 +379,7 @@ def list_image_queries(self, **kwargs): Keyword Args: page (int): A page number within the paginated result set.. [optional] page_size (int): Number of items to return per page.. [optional] + predictor_id (str): Optionally filter image queries by detector ID.. [optional] _return_http_data_only (bool): response data without head status code and headers. Default is True. _preload_content (bool): if False, the urllib3.HTTPResponse object diff --git a/generated/groundlight_openapi_client/model/patched_detector_request.py b/generated/groundlight_openapi_client/model/patched_detector_request.py index 64534047..251cb75d 100644 --- a/generated/groundlight_openapi_client/model/patched_detector_request.py +++ b/generated/groundlight_openapi_client/model/patched_detector_request.py @@ -8,7 +8,6 @@ Generated by: https://openapi-generator.tech """ - import re # noqa: F401 import sys # noqa: F401 @@ -25,7 +24,7 @@ file_type, none_type, validate_get_composed_info, - OpenApiModel + OpenApiModel, ) from groundlight_openapi_client.exceptions import ApiAttributeError @@ -34,9 +33,10 @@ def lazy_import(): from groundlight_openapi_client.model.blank_enum import BlankEnum from groundlight_openapi_client.model.escalation_type_enum import EscalationTypeEnum from groundlight_openapi_client.model.status_enum import StatusEnum - globals()['BlankEnum'] = BlankEnum - globals()['EscalationTypeEnum'] = EscalationTypeEnum - globals()['StatusEnum'] = StatusEnum + + globals()["BlankEnum"] = BlankEnum + globals()["EscalationTypeEnum"] = EscalationTypeEnum + globals()["StatusEnum"] = StatusEnum class PatchedDetectorRequest(ModelNormal): @@ -63,21 +63,20 @@ class PatchedDetectorRequest(ModelNormal): as additional properties values. """ - allowed_values = { - } + allowed_values = {} validations = { - ('name',): { - 'max_length': 200, - 'min_length': 1, + ("name",): { + "max_length": 200, + "min_length": 1, }, - ('confidence_threshold',): { - 'inclusive_maximum': 1.0, - 'inclusive_minimum': 0.0, + ("confidence_threshold",): { + "inclusive_maximum": 1.0, + "inclusive_minimum": 0.0, }, - ('patience_time',): { - 'inclusive_maximum': 3600, - 'inclusive_minimum': 0, + ("patience_time",): { + "inclusive_maximum": 3600, + "inclusive_minimum": 0, }, } @@ -88,7 +87,17 @@ def additional_properties_type(): of type self, this must run after the class is loaded """ lazy_import() - return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501 + return ( + bool, + date, + datetime, + dict, + float, + int, + list, + str, + none_type, + ) # noqa: E501 _nullable = False @@ -104,28 +113,46 @@ def openapi_types(): """ lazy_import() return { - 'name': (str,), # noqa: E501 - 'confidence_threshold': (float,), # noqa: E501 - 'patience_time': (float,), # noqa: E501 - 'status': (bool, date, datetime, dict, float, int, list, str, none_type,), # noqa: E501 - 'escalation_type': (bool, date, datetime, dict, float, int, list, str, none_type,), # noqa: E501 + "name": (str,), # noqa: E501 + "confidence_threshold": (float,), # noqa: E501 + "patience_time": (float,), # noqa: E501 + "status": ( + bool, + date, + datetime, + dict, + float, + int, + list, + str, + none_type, + ), # noqa: E501 + "escalation_type": ( + bool, + date, + datetime, + dict, + float, + int, + list, + str, + none_type, + ), # noqa: E501 } @cached_property def discriminator(): return None - attribute_map = { - 'name': 'name', # noqa: E501 - 'confidence_threshold': 'confidence_threshold', # noqa: E501 - 'patience_time': 'patience_time', # noqa: E501 - 'status': 'status', # noqa: E501 - 'escalation_type': 'escalation_type', # noqa: E501 + "name": "name", # noqa: E501 + "confidence_threshold": "confidence_threshold", # noqa: E501 + "patience_time": "patience_time", # noqa: E501 + "status": "status", # noqa: E501 + "escalation_type": "escalation_type", # noqa: E501 } - read_only_vars = { - } + read_only_vars = {} _composed_schemas = {} @@ -172,17 +199,18 @@ def _from_openapi_data(cls, *args, **kwargs): # noqa: E501 escalation_type (bool, date, datetime, dict, float, int, list, str, none_type): Category that define internal proccess for labeling image queries * `STANDARD` - STANDARD * `NO_HUMAN_LABELING` - NO_HUMAN_LABELING. [optional] # noqa: E501 """ - _check_type = kwargs.pop('_check_type', True) - _spec_property_naming = kwargs.pop('_spec_property_naming', False) - _path_to_item = kwargs.pop('_path_to_item', ()) - _configuration = kwargs.pop('_configuration', None) - _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _path_to_item = kwargs.pop("_path_to_item", ()) + _configuration = kwargs.pop("_configuration", None) + _visited_composed_classes = kwargs.pop("_visited_composed_classes", ()) self = super(OpenApiModel, cls).__new__(cls) if args: raise ApiTypeError( - "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." + % ( args, self.__class__.__name__, ), @@ -198,22 +226,24 @@ def _from_openapi_data(cls, *args, **kwargs): # noqa: E501 self._visited_composed_classes = _visited_composed_classes + (self.__class__,) for var_name, var_value in kwargs.items(): - if var_name not in self.attribute_map and \ - self._configuration is not None and \ - self._configuration.discard_unknown_keys and \ - self.additional_properties_type is None: + if ( + var_name not in self.attribute_map + and self._configuration is not None + and self._configuration.discard_unknown_keys + and self.additional_properties_type is None + ): # discard variable. continue setattr(self, var_name, var_value) return self required_properties = set([ - '_data_store', - '_check_type', - '_spec_property_naming', - '_path_to_item', - '_configuration', - '_visited_composed_classes', + "_data_store", + "_check_type", + "_spec_property_naming", + "_path_to_item", + "_configuration", + "_visited_composed_classes", ]) @convert_js_args_to_python_args @@ -258,15 +288,16 @@ def __init__(self, *args, **kwargs): # noqa: E501 escalation_type (bool, date, datetime, dict, float, int, list, str, none_type): Category that define internal proccess for labeling image queries * `STANDARD` - STANDARD * `NO_HUMAN_LABELING` - NO_HUMAN_LABELING. [optional] # noqa: E501 """ - _check_type = kwargs.pop('_check_type', True) - _spec_property_naming = kwargs.pop('_spec_property_naming', False) - _path_to_item = kwargs.pop('_path_to_item', ()) - _configuration = kwargs.pop('_configuration', None) - _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _path_to_item = kwargs.pop("_path_to_item", ()) + _configuration = kwargs.pop("_configuration", None) + _visited_composed_classes = kwargs.pop("_visited_composed_classes", ()) if args: raise ApiTypeError( - "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." + % ( args, self.__class__.__name__, ), @@ -282,13 +313,17 @@ def __init__(self, *args, **kwargs): # noqa: E501 self._visited_composed_classes = _visited_composed_classes + (self.__class__,) for var_name, var_value in kwargs.items(): - if var_name not in self.attribute_map and \ - self._configuration is not None and \ - self._configuration.discard_unknown_keys and \ - self.additional_properties_type is None: + if ( + var_name not in self.attribute_map + and self._configuration is not None + and self._configuration.discard_unknown_keys + and self.additional_properties_type is None + ): # discard variable. continue setattr(self, var_name, var_value) if var_name in self.read_only_vars: - raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate " - f"class with read only attributes.") + raise ApiAttributeError( + f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate " + "class with read only attributes." + ) diff --git a/generated/model.py b/generated/model.py index 25027f04..827e7ea1 100644 --- a/generated/model.py +++ b/generated/model.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: public-api.yaml -# timestamp: 2024-12-09T18:29:17+00:00 +# timestamp: 2024-12-10T01:13:13+00:00 from __future__ import annotations diff --git a/spec/public-api.yaml b/spec/public-api.yaml index 89f9f0c0..084cc956 100644 --- a/spec/public-api.yaml +++ b/spec/public-api.yaml @@ -323,6 +323,11 @@ paths: schema: type: integer description: Number of items to return per page. + - in: query + name: predictor_id + schema: + type: string + description: Optionally filter image queries by detector ID. tags: - image-queries security: @@ -1457,4 +1462,4 @@ servers: - url: https://device.positronix.ai/device-api description: Device Prod - url: https://device.integ.positronix.ai/device-api - description: Device Integ \ No newline at end of file + description: Device Integ diff --git a/src/groundlight/client.py b/src/groundlight/client.py index b45300bc..f3c557d2 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -528,7 +528,9 @@ def get_image_query(self, id: str) -> ImageQuery: # pylint: disable=redefined-b iq = ImageQuery.parse_obj(obj.to_dict()) return self._fixup_image_query(iq) - def list_image_queries(self, page: int = 1, page_size: int = 10) -> PaginatedImageQueryList: + def list_image_queries( + self, page: int = 1, page_size: int = 10, detector_id: Union[str, None] = None + ) -> PaginatedImageQueryList: """ List all image queries associated with your account, with pagination support. @@ -550,9 +552,10 @@ def list_image_queries(self, page: int = 1, page_size: int = 10) -> PaginatedIma :return: PaginatedImageQueryList containing the requested page of image queries and pagination metadata like total count and links to next/previous pages. """ - obj = self.image_queries_api.list_image_queries( - page=page, page_size=page_size, _request_timeout=DEFAULT_REQUEST_TIMEOUT - ) + params = {"page": page, "page_size": page_size, "_request_timeout": DEFAULT_REQUEST_TIMEOUT} + if detector_id: + params["detector_id"] = detector_id + obj = self.image_queries_api.list_image_queries(**params) image_queries = PaginatedImageQueryList.parse_obj(obj.to_dict()) if image_queries.results is not None: image_queries.results = [self._fixup_image_query(iq) for iq in image_queries.results] diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 01a22bc2..6cfbcb2a 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -576,6 +576,19 @@ def test_list_image_queries(gl: Groundlight): assert is_valid_display_result(image_query.result) +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?") + 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] + + image_queries = gl.list_image_queries(detector_id=detector.id) + assert len(image_queries.results) == 2 + for image_query in image_queries.results: + assert image_query.id in iq_ids + + def test_get_image_query(gl: Groundlight, image_query_yes: ImageQuery): _image_query = gl.get_image_query(id=image_query_yes.id) assert str(_image_query) From b494f6abb728b0000010bfcce63227c6a6c83142 Mon Sep 17 00:00:00 2001 From: brandon Date: Mon, 9 Dec 2024 17:18:17 -0800 Subject: [PATCH 2/4] You'll never guess this one cool trick that linters hate --- test/integration/test_groundlight.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 6cfbcb2a..b0285230 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -584,7 +584,8 @@ def test_list_image_queries_with_filter(gl: Groundlight): iq_ids = [image_query_yes.id, image_query_no.id] image_queries = gl.list_image_queries(detector_id=detector.id) - assert len(image_queries.results) == 2 + num_image_queries = 2 + assert len(image_queries.results) == num_image_queries for image_query in image_queries.results: assert image_query.id in iq_ids From b52a04b75b5e7280dc2ba6922acff83a49980d9f Mon Sep 17 00:00:00 2001 From: brandon Date: Mon, 9 Dec 2024 17:25:30 -0800 Subject: [PATCH 3/4] I can make the values of my dictionary anything I like! --- src/groundlight/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/groundlight/client.py b/src/groundlight/client.py index f3c557d2..61c21175 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -5,7 +5,7 @@ import warnings from functools import partial from io import BufferedReader, BytesIO -from typing import Callable, List, Optional, Union +from typing import Any, Callable, List, Optional, Union from groundlight_openapi_client import Configuration from groundlight_openapi_client.api.detectors_api import DetectorsApi @@ -552,7 +552,7 @@ def list_image_queries( :return: PaginatedImageQueryList containing the requested page of image queries and pagination metadata like total count and links to next/previous pages. """ - params = {"page": page, "page_size": page_size, "_request_timeout": DEFAULT_REQUEST_TIMEOUT} + params: dict[str, Any] = {"page": page, "page_size": page_size, "_request_timeout": DEFAULT_REQUEST_TIMEOUT} if detector_id: params["detector_id"] = detector_id obj = self.image_queries_api.list_image_queries(**params) From ed3aa61110bdae06b650d52dc12a0b8f0c6d0982 Mon Sep 17 00:00:00 2001 From: brandon Date: Fri, 13 Dec 2024 12:05:37 -0800 Subject: [PATCH 4/4] update spec with backend update --- generated/docs/ImageQueriesApi.md | 6 +++--- .../api/image_queries_api.py | 10 +++++----- generated/model.py | 2 +- spec/public-api.yaml | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/generated/docs/ImageQueriesApi.md b/generated/docs/ImageQueriesApi.md index 47a65ae5..3d405374 100644 --- a/generated/docs/ImageQueriesApi.md +++ b/generated/docs/ImageQueriesApi.md @@ -203,12 +203,12 @@ with groundlight_openapi_client.ApiClient(configuration) as api_client: api_instance = image_queries_api.ImageQueriesApi(api_client) page = 1 # int | A page number within the paginated result set. (optional) page_size = 1 # int | Number of items to return per page. (optional) - predictor_id = "predictor_id_example" # str | Optionally filter image queries by detector ID. (optional) + detector_id = "detector_id_example" # str | Optionally filter image queries by detector ID. (optional) # example passing only required values which don't have defaults set # and optional values try: - api_response = api_instance.list_image_queries(page=page, page_size=page_size, predictor_id=predictor_id) + api_response = api_instance.list_image_queries(page=page, page_size=page_size, detector_id=detector_id) pprint(api_response) except groundlight_openapi_client.ApiException as e: print("Exception when calling ImageQueriesApi->list_image_queries: %s\n" % e) @@ -221,7 +221,7 @@ Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **page** | **int**| A page number within the paginated result set. | [optional] **page_size** | **int**| Number of items to return per page. | [optional] - **predictor_id** | **str**| Optionally filter image queries by detector ID. | [optional] + **detector_id** | **str**| Optionally filter image queries by detector ID. | [optional] ### Return type diff --git a/generated/groundlight_openapi_client/api/image_queries_api.py b/generated/groundlight_openapi_client/api/image_queries_api.py index b25ad772..906bb202 100644 --- a/generated/groundlight_openapi_client/api/image_queries_api.py +++ b/generated/groundlight_openapi_client/api/image_queries_api.py @@ -129,7 +129,7 @@ def __init__(self, api_client=None): "all": [ "page", "page_size", - "predictor_id", + "detector_id", ], "required": [], "nullable": [], @@ -142,17 +142,17 @@ def __init__(self, api_client=None): "openapi_types": { "page": (int,), "page_size": (int,), - "predictor_id": (str,), + "detector_id": (str,), }, "attribute_map": { "page": "page", "page_size": "page_size", - "predictor_id": "predictor_id", + "detector_id": "detector_id", }, "location_map": { "page": "query", "page_size": "query", - "predictor_id": "query", + "detector_id": "query", }, "collection_format_map": {}, }, @@ -379,7 +379,7 @@ def list_image_queries(self, **kwargs): Keyword Args: page (int): A page number within the paginated result set.. [optional] page_size (int): Number of items to return per page.. [optional] - predictor_id (str): Optionally filter image queries by detector ID.. [optional] + detector_id (str): Optionally filter image queries by detector ID.. [optional] _return_http_data_only (bool): response data without head status code and headers. Default is True. _preload_content (bool): if False, the urllib3.HTTPResponse object diff --git a/generated/model.py b/generated/model.py index 827e7ea1..12eef27d 100644 --- a/generated/model.py +++ b/generated/model.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: public-api.yaml -# timestamp: 2024-12-10T01:13:13+00:00 +# timestamp: 2024-12-13T20:04:02+00:00 from __future__ import annotations diff --git a/spec/public-api.yaml b/spec/public-api.yaml index 084cc956..10e8ecaa 100644 --- a/spec/public-api.yaml +++ b/spec/public-api.yaml @@ -324,7 +324,7 @@ paths: type: integer description: Number of items to return per page. - in: query - name: predictor_id + name: detector_id schema: type: string description: Optionally filter image queries by detector ID.