From 42f53d5209bbe418328ee751db8d46e37f4b4bd1 Mon Sep 17 00:00:00 2001 From: brandon Date: Wed, 4 Dec 2024 00:57:36 +0000 Subject: [PATCH 01/17] Adds sdk label support for multiclass detectors --- src/groundlight/binary_labels.py | 22 ---------- src/groundlight/client.py | 41 +++++++++++++------ src/groundlight/experimental_api.py | 62 +---------------------------- test/unit/test_labels.py | 56 ++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 95 deletions(-) create mode 100644 test/unit/test_labels.py diff --git a/src/groundlight/binary_labels.py b/src/groundlight/binary_labels.py index c1d20470..557a4245 100644 --- a/src/groundlight/binary_labels.py +++ b/src/groundlight/binary_labels.py @@ -53,25 +53,3 @@ def convert_internal_label_to_display( logger.warning(f"Unrecognized internal label {label} - leaving it alone as a string.") return label - - -def convert_display_label_to_internal( - context: Union[ImageQuery, Detector, str], # pylint: disable=unused-argument - label: Union[Label, str], -) -> str: - """Convert a label that comes from the user into the label string that we send to the server. We - are strict here, and only allow YES/NO. - - NOTE: We accept case-insensitive label strings from the user, but we send UPPERCASE labels to - the server. E.g., user inputs "yes" -> the label is returned as "YES". - """ - # NOTE: In the future we should validate against actually supported labels for the detector - if not isinstance(label, str): - raise ValueError(f"Expected a string label, but got {label} of type {type(label)}") - upper = label.upper() - if upper == Label.YES: - return DeprecatedLabel.PASS.value - if upper == Label.NO: - return DeprecatedLabel.FAIL.value - - raise ValueError(f"Invalid label string '{label}'. Must be one of '{Label.YES.value}','{Label.NO.value}'.") diff --git a/src/groundlight/client.py b/src/groundlight/client.py index 14ab15af..14bb9a2d 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -13,9 +13,11 @@ from groundlight_openapi_client.api.labels_api import LabelsApi from groundlight_openapi_client.api.user_api import UserApi from groundlight_openapi_client.exceptions import NotFoundException, UnauthorizedException +from groundlight_openapi_client.model.b_box_geometry_request import BBoxGeometryRequest from groundlight_openapi_client.model.detector_creation_input_request import DetectorCreationInputRequest from groundlight_openapi_client.model.label_value_request import LabelValueRequest from groundlight_openapi_client.model.patched_detector_request import PatchedDetectorRequest +from groundlight_openapi_client.model.roi_request import ROIRequest from model import ( ROI, BinaryClassificationResult, @@ -26,7 +28,7 @@ ) from urllib3.exceptions import InsecureRequestWarning -from groundlight.binary_labels import Label, convert_display_label_to_internal, convert_internal_label_to_display +from groundlight.binary_labels import Label, convert_internal_label_to_display from groundlight.config import API_TOKEN_MISSING_HELP_MESSAGE, API_TOKEN_VARIABLE_NAME, DISABLE_TLS_VARIABLE_NAME from groundlight.encodings import url_encode_dict from groundlight.images import ByteStreamWrapper, parse_supported_image_types @@ -1066,8 +1068,9 @@ def _wait_for_result( image_query = self._fixup_image_query(image_query) return image_query + # pylint: disable=duplicate-code def add_label( - self, image_query: Union[ImageQuery, str], label: Union[Label, str], rois: Union[List[ROI], str, None] = None + self, image_query: Union[ImageQuery, str], label: Union[Label, int, str], rois: Union[List[ROI], str, None] = None ): """ Provide a new label (annotation) for an image query. This is used to provide ground-truth labels @@ -1075,7 +1078,7 @@ def add_label( **Example usage**:: - gl = Groundlight() + gl = ExperimentalApi() # Using an ImageQuery object image_query = gl.ask_ml(detector_id, image_data) @@ -1088,27 +1091,41 @@ def add_label( rois = [ROI(x=100, y=100, width=50, height=50)] gl.add_label(image_query, "YES", rois=rois) - :param image_query: Either an ImageQuery object (returned from methods like :meth:`ask_ml`) or an image query ID - string starting with "iq_". - :param label: The label value to assign, typically "YES" or "NO" for binary classification detectors. - For multi-class detectors, use one of the defined class names. - :param rois: Optional list of ROI objects defining regions of interest in the image. - Each ROI specifies a bounding box with x, y coordinates and width, height. + :param image_query: Either an ImageQuery object (returned from methods like + `ask_ml`) or an image query ID string starting with "iq_". + + :param label: The label value to assign, typically "YES" or "NO" for binary + classification detectors. For multi-class detectors, use one of + the defined class names. + + :param rois: Optional list of ROI objects defining regions of interest in the + image. Each ROI specifies a bounding box with x, y coordinates + and width, height. :return: None """ if isinstance(rois, str): raise TypeError("rois must be a list of ROI objects. CLI support is not implemented") + if isinstance(label, int): + label = str(label) if isinstance(image_query, ImageQuery): image_query_id = image_query.id else: image_query_id = str(image_query) # Some old imagequery id's started with "chk_" + # TODO: handle iqe_ for image_queries returned from edge endpoints if not image_query_id.startswith(("chk_", "iq_")): raise ValueError(f"Invalid image query id {image_query_id}") - api_label = convert_display_label_to_internal(image_query_id, label) - rois_json = [roi.dict() for roi in rois] if rois else None - request_params = LabelValueRequest(label=api_label, image_query_id=image_query_id, rois=rois_json) + geometry_requests = [BBoxGeometryRequest(**roi.geometry.dict()) for roi in rois] if rois else None + roi_requests = ( + [ + ROIRequest(label=roi.label, score=roi.score, geometry=geometry) + for roi, geometry in zip(rois, geometry_requests) + ] + if rois and geometry_requests + else None + ) + request_params = LabelValueRequest(label=label, image_query_id=image_query_id, rois=roi_requests) self.labels_api.create_label(request_params) def start_inspection(self) -> str: diff --git a/src/groundlight/experimental_api.py b/src/groundlight/experimental_api.py index aac00fa5..1a8c34cd 100644 --- a/src/groundlight/experimental_api.py +++ b/src/groundlight/experimental_api.py @@ -32,7 +32,7 @@ from groundlight_openapi_client.model.verb_enum import VerbEnum from model import ROI, BBoxGeometry, Detector, DetectorGroup, ImageQuery, ModeEnum, PaginatedRuleList, Rule -from groundlight.binary_labels import Label, convert_display_label_to_internal +from groundlight.binary_labels import Label from groundlight.images import parse_supported_image_types from groundlight.optional_imports import Image, np @@ -499,66 +499,6 @@ def create_roi(self, label: str, top_left: Tuple[float, float], bottom_right: Tu ), ) - # TODO: remove duplicate method on subclass - # pylint: disable=duplicate-code - def add_label( - self, image_query: Union[ImageQuery, str], label: Union[Label, str], rois: Union[List[ROI], str, None] = None - ): - """ - Provide a new label (annotation) for an image query. This is used to provide ground-truth labels - for training detectors, or to correct the results of detectors. - - **Example usage**:: - - gl = ExperimentalApi() - - # Using an ImageQuery object - image_query = gl.ask_ml(detector_id, image_data) - gl.add_label(image_query, "YES") - - # Using an image query ID string directly - gl.add_label("iq_abc123", "NO") - - # With regions of interest (ROIs) - rois = [ROI(x=100, y=100, width=50, height=50)] - gl.add_label(image_query, "YES", rois=rois) - - :param image_query: Either an ImageQuery object (returned from methods like - `ask_ml`) or an image query ID string starting with "iq_". - - :param label: The label value to assign, typically "YES" or "NO" for binary - classification detectors. For multi-class detectors, use one of - the defined class names. - - :param rois: Optional list of ROI objects defining regions of interest in the - image. Each ROI specifies a bounding box with x, y coordinates - and width, height. - - :return: None - """ - if isinstance(rois, str): - raise TypeError("rois must be a list of ROI objects. CLI support is not implemented") - if isinstance(image_query, ImageQuery): - image_query_id = image_query.id - else: - image_query_id = str(image_query) - # Some old imagequery id's started with "chk_" - # TODO: handle iqe_ for image_queries returned from edge endpoints - if not image_query_id.startswith(("chk_", "iq_")): - raise ValueError(f"Invalid image query id {image_query_id}") - api_label = convert_display_label_to_internal(image_query_id, label) - geometry_requests = [BBoxGeometryRequest(**roi.geometry.dict()) for roi in rois] if rois else None - roi_requests = ( - [ - ROIRequest(label=roi.label, score=roi.score, geometry=geometry) - for roi, geometry in zip(rois, geometry_requests) - ] - if rois and geometry_requests - else None - ) - request_params = LabelValueRequest(label=api_label, image_query_id=image_query_id, rois=roi_requests) - self.labels_api.create_label(request_params) - def reset_detector(self, detector: Union[str, Detector]) -> None: """ Removes all image queries and training data for the given detector. This effectively resets diff --git a/test/unit/test_labels.py b/test/unit/test_labels.py new file mode 100644 index 00000000..474c6ae0 --- /dev/null +++ b/test/unit/test_labels.py @@ -0,0 +1,56 @@ +from datetime import datetime + +import pytest +from groundlight import ExperimentalApi, ApiException + + +def test_binary_labels(gl_experimental: ExperimentalApi): + name = f"Test binary labels{datetime.utcnow()}" + det = gl_experimental.create_detector(name, "test_query") + iq1 = gl_experimental.submit_image_query(det, "test/assets/cat.jpeg") + gl_experimental.add_label(iq1, "YES") + iq1 = gl_experimental.get_image_query(iq1.id) + assert iq1.result.label == "YES" + gl_experimental.add_label(iq1, "NO") + iq1 = gl_experimental.get_image_query(iq1.id) + assert iq1.result.label == "NO" + gl_experimental.add_label(iq1, "UNCLEAR") + iq1 = gl_experimental.get_image_query(iq1.id) + assert iq1.result.label == "UNCLEAR" + with pytest.raises(ApiException) as _: + gl_experimental.add_label(iq1, "MAYBE") + +def test_counting_labels(gl_experimental: ExperimentalApi): + name = f"Test binary labels{datetime.utcnow()}" + det = gl_experimental.create_counting_detector(name, "test_query") + iq1 = gl_experimental.submit_image_query(det, "test/assets/cat.jpeg") + gl_experimental.add_label(iq1, 0) + iq1 = gl_experimental.get_image_query(iq1.id) + assert iq1.result.count == 0 + gl_experimental.add_label(iq1, 5) + iq1 = gl_experimental.get_image_query(iq1.id) + assert iq1.result.count == 5 + with pytest.raises(ApiException) as _: + gl_experimental.add_label(iq1, "MAYBE") + with pytest.raises(ApiException) as _: + gl_experimental.add_label(iq1, -999) + +def test_multiclass_labels(gl_experimental: ExperimentalApi): + name = f"Test binary labels{datetime.utcnow()}" + det = gl_experimental.create_multiclass_detector(name, "test_query", class_names=["apple", "banana", "cherry"]) + iq1 = gl_experimental.submit_image_query(det, "test/assets/cat.jpeg") + gl_experimental.add_label(iq1, "apple") + iq1 = gl_experimental.get_image_query(iq1.id) + assert iq1.result.label == "apple" + gl_experimental.add_label(iq1, "banana") + iq1 = gl_experimental.get_image_query(iq1.id) + assert iq1.result.label == "banana" + gl_experimental.add_label(iq1, "cherry") + iq1 = gl_experimental.get_image_query(iq1.id) + assert iq1.result.label == "cherry" + # You can submit the index of the class as well + gl_experimental.add_label(iq1, 2) + iq1 = gl_experimental.get_image_query(iq1.id) + assert iq1.result.label == "cherry" + with pytest.raises(ApiException) as _: + gl_experimental.add_label(iq1, "MAYBE") From 4097ec94dd6087e75e0132c17ca87f0399f4a056 Mon Sep 17 00:00:00 2001 From: Auto-format Bot Date: Wed, 4 Dec 2024 00:58:43 +0000 Subject: [PATCH 02/17] Automatically reformatting code --- src/groundlight/client.py | 5 ++++- src/groundlight/experimental_api.py | 6 +----- test/unit/test_labels.py | 4 +++- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/groundlight/client.py b/src/groundlight/client.py index 14bb9a2d..4adaa9d2 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -1070,7 +1070,10 @@ def _wait_for_result( # pylint: disable=duplicate-code def add_label( - self, image_query: Union[ImageQuery, str], label: Union[Label, int, str], rois: Union[List[ROI], str, None] = None + self, + image_query: Union[ImageQuery, str], + label: Union[Label, int, str], + rois: Union[List[ROI], str, None] = None, ): """ Provide a new label (annotation) for an image query. This is used to provide ground-truth labels diff --git a/src/groundlight/experimental_api.py b/src/groundlight/experimental_api.py index 1a8c34cd..56d48a1d 100644 --- a/src/groundlight/experimental_api.py +++ b/src/groundlight/experimental_api.py @@ -17,22 +17,18 @@ from groundlight_openapi_client.api.image_queries_api import ImageQueriesApi from groundlight_openapi_client.api.notes_api import NotesApi from groundlight_openapi_client.model.action_request import ActionRequest -from groundlight_openapi_client.model.b_box_geometry_request import BBoxGeometryRequest from groundlight_openapi_client.model.channel_enum import ChannelEnum from groundlight_openapi_client.model.condition_request import ConditionRequest from groundlight_openapi_client.model.count_mode_configuration import CountModeConfiguration from groundlight_openapi_client.model.detector_group_request import DetectorGroupRequest from groundlight_openapi_client.model.escalation_type_enum import EscalationTypeEnum -from groundlight_openapi_client.model.label_value_request import LabelValueRequest from groundlight_openapi_client.model.multi_class_mode_configuration import MultiClassModeConfiguration from groundlight_openapi_client.model.patched_detector_request import PatchedDetectorRequest -from groundlight_openapi_client.model.roi_request import ROIRequest from groundlight_openapi_client.model.rule_request import RuleRequest from groundlight_openapi_client.model.status_enum import StatusEnum from groundlight_openapi_client.model.verb_enum import VerbEnum -from model import ROI, BBoxGeometry, Detector, DetectorGroup, ImageQuery, ModeEnum, PaginatedRuleList, Rule +from model import ROI, BBoxGeometry, Detector, DetectorGroup, ModeEnum, PaginatedRuleList, Rule -from groundlight.binary_labels import Label from groundlight.images import parse_supported_image_types from groundlight.optional_imports import Image, np diff --git a/test/unit/test_labels.py b/test/unit/test_labels.py index 474c6ae0..a2e3c37e 100644 --- a/test/unit/test_labels.py +++ b/test/unit/test_labels.py @@ -1,7 +1,7 @@ from datetime import datetime import pytest -from groundlight import ExperimentalApi, ApiException +from groundlight import ApiException, ExperimentalApi def test_binary_labels(gl_experimental: ExperimentalApi): @@ -20,6 +20,7 @@ def test_binary_labels(gl_experimental: ExperimentalApi): with pytest.raises(ApiException) as _: gl_experimental.add_label(iq1, "MAYBE") + def test_counting_labels(gl_experimental: ExperimentalApi): name = f"Test binary labels{datetime.utcnow()}" det = gl_experimental.create_counting_detector(name, "test_query") @@ -35,6 +36,7 @@ def test_counting_labels(gl_experimental: ExperimentalApi): with pytest.raises(ApiException) as _: gl_experimental.add_label(iq1, -999) + def test_multiclass_labels(gl_experimental: ExperimentalApi): name = f"Test binary labels{datetime.utcnow()}" det = gl_experimental.create_multiclass_detector(name, "test_query", class_names=["apple", "banana", "cherry"]) From 24a0c4a2a8c7101d62a1adfe98aa6ed17b89ca86 Mon Sep 17 00:00:00 2001 From: brandon Date: Wed, 4 Dec 2024 01:22:58 +0000 Subject: [PATCH 03/17] fixing comments --- src/groundlight/client.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/groundlight/client.py b/src/groundlight/client.py index 14bb9a2d..4da9651e 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -1068,7 +1068,6 @@ def _wait_for_result( image_query = self._fixup_image_query(image_query) return image_query - # pylint: disable=duplicate-code def add_label( self, image_query: Union[ImageQuery, str], label: Union[Label, int, str], rois: Union[List[ROI], str, None] = None ): @@ -1078,7 +1077,7 @@ def add_label( **Example usage**:: - gl = ExperimentalApi() + gl = Groundlight() # Using an ImageQuery object image_query = gl.ask_ml(detector_id, image_data) @@ -1113,7 +1112,6 @@ def add_label( else: image_query_id = str(image_query) # Some old imagequery id's started with "chk_" - # TODO: handle iqe_ for image_queries returned from edge endpoints if not image_query_id.startswith(("chk_", "iq_")): raise ValueError(f"Invalid image query id {image_query_id}") geometry_requests = [BBoxGeometryRequest(**roi.geometry.dict()) for roi in rois] if rois else None From bf3974152efa7ec507669c314620a6722c46d677 Mon Sep 17 00:00:00 2001 From: brandon Date: Mon, 9 Dec 2024 17:14:02 -0800 Subject: [PATCH 04/17] 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 eee8808a3f580c0496a11e0d84a7b8621f4fa61e Mon Sep 17 00:00:00 2001 From: brandon Date: Mon, 9 Dec 2024 17:22:43 -0800 Subject: [PATCH 05/17] linter love --- test/unit/test_labels.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/unit/test_labels.py b/test/unit/test_labels.py index a2e3c37e..0071dbc5 100644 --- a/test/unit/test_labels.py +++ b/test/unit/test_labels.py @@ -28,9 +28,10 @@ def test_counting_labels(gl_experimental: ExperimentalApi): gl_experimental.add_label(iq1, 0) iq1 = gl_experimental.get_image_query(iq1.id) assert iq1.result.count == 0 - gl_experimental.add_label(iq1, 5) + good_label = 5 + gl_experimental.add_label(iq1, good_label) iq1 = gl_experimental.get_image_query(iq1.id) - assert iq1.result.count == 5 + assert iq1.result.count == good_label with pytest.raises(ApiException) as _: gl_experimental.add_label(iq1, "MAYBE") with pytest.raises(ApiException) as _: From 10e8dcc0bb90e75628947262646677972396b176 Mon Sep 17 00:00:00 2001 From: brandon Date: Mon, 9 Dec 2024 17:33:00 -0800 Subject: [PATCH 06/17] equivilent-ish exchange of pylint disables --- .pylintrc | 2 +- src/groundlight/internalapi.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pylintrc b/.pylintrc index 8838f39f..588224e4 100644 --- a/.pylintrc +++ b/.pylintrc @@ -87,10 +87,10 @@ disable=raw-checker-failed, missing-class-docstring, missing-function-docstring, invalid-name, - too-few-public-methods, line-too-long, import-error, # we only install linter dependencies in CI/CD wrong-import-order, # we use ruff to enforce import order + duplicate-code, # pylint has a tendancy to capture docstrings as duplicate code # Enable the message, report, category or checker with the given id(s). You can diff --git a/src/groundlight/internalapi.py b/src/groundlight/internalapi.py index f5a2e45b..25506d5a 100644 --- a/src/groundlight/internalapi.py +++ b/src/groundlight/internalapi.py @@ -87,7 +87,7 @@ def __init__(self, status=None, reason=None, http_resp=None): super().__init__(status, reason, http_resp) -class RequestsRetryDecorator: +class RequestsRetryDecorator: # pylint: disable=too-few-public-methods """ Decorate a function to retry sending HTTP requests. From 81f6822db6a50d7848dac6ade6c4f07b1214bef1 Mon Sep 17 00:00:00 2001 From: Auto-format Bot Date: Tue, 10 Dec 2024 01:34:16 +0000 Subject: [PATCH 07/17] Automatically reformatting code --- src/groundlight/internalapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/groundlight/internalapi.py b/src/groundlight/internalapi.py index 25506d5a..18f2f3a7 100644 --- a/src/groundlight/internalapi.py +++ b/src/groundlight/internalapi.py @@ -87,7 +87,7 @@ def __init__(self, status=None, reason=None, http_resp=None): super().__init__(status, reason, http_resp) -class RequestsRetryDecorator: # pylint: disable=too-few-public-methods +class RequestsRetryDecorator: # pylint: disable=too-few-public-methods """ Decorate a function to retry sending HTTP requests. From c4106a486bef501d4887a6f1b50cf0b9da773863 Mon Sep 17 00:00:00 2001 From: brandon Date: Mon, 9 Dec 2024 17:36:20 -0800 Subject: [PATCH 08/17] fix class_name --- test/unit/test_labels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/test_labels.py b/test/unit/test_labels.py index 0071dbc5..d894801d 100644 --- a/test/unit/test_labels.py +++ b/test/unit/test_labels.py @@ -23,7 +23,7 @@ def test_binary_labels(gl_experimental: ExperimentalApi): def test_counting_labels(gl_experimental: ExperimentalApi): name = f"Test binary labels{datetime.utcnow()}" - det = gl_experimental.create_counting_detector(name, "test_query") + det = gl_experimental.create_counting_detector(name, "test_query", "test_object_class") iq1 = gl_experimental.submit_image_query(det, "test/assets/cat.jpeg") gl_experimental.add_label(iq1, 0) iq1 = gl_experimental.get_image_query(iq1.id) From 752a52be37a66140379d1d6246f2a20240af6120 Mon Sep 17 00:00:00 2001 From: brandon Date: Mon, 9 Dec 2024 17:49:21 -0800 Subject: [PATCH 09/17] manually reverting a bad merge --- spec/public-api.yaml | 7 +------ src/groundlight/client.py | 9 ++++----- test/integration/test_groundlight.py | 13 ------------- 3 files changed, 5 insertions(+), 24 deletions(-) diff --git a/spec/public-api.yaml b/spec/public-api.yaml index 084cc956..89f9f0c0 100644 --- a/spec/public-api.yaml +++ b/spec/public-api.yaml @@ -323,11 +323,6 @@ 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: @@ -1462,4 +1457,4 @@ servers: - url: https://device.positronix.ai/device-api description: Device Prod - url: https://device.integ.positronix.ai/device-api - description: Device Integ + description: Device Integ \ No newline at end of file diff --git a/src/groundlight/client.py b/src/groundlight/client.py index 452a7232..05a863e0 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -531,7 +531,7 @@ def get_image_query(self, id: str) -> ImageQuery: # pylint: disable=redefined-b return self._fixup_image_query(iq) def list_image_queries( - self, page: int = 1, page_size: int = 10, detector_id: Union[str, None] = None + self, page: int = 1, page_size: int = 10 ) -> PaginatedImageQueryList: """ List all image queries associated with your account, with pagination support. @@ -554,10 +554,9 @@ 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} - if detector_id: - params["detector_id"] = detector_id - obj = self.image_queries_api.list_image_queries(**params) + obj = self.image_queries_api.list_image_queries( + page=page, page_size=page_size, _request_timeout=DEFAULT_REQUEST_TIMEOUT + ) 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 6cfbcb2a..01a22bc2 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -576,19 +576,6 @@ 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 a53cdb8582e822311f58390b4bd616cf080675b0 Mon Sep 17 00:00:00 2001 From: Auto-format Bot Date: Tue, 10 Dec 2024 01:50:17 +0000 Subject: [PATCH 10/17] Automatically reformatting code --- src/groundlight/client.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/groundlight/client.py b/src/groundlight/client.py index 05a863e0..41adf1a2 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -530,9 +530,7 @@ 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) -> PaginatedImageQueryList: """ List all image queries associated with your account, with pagination support. From fb0606712488a425a31945db6cd5efe3920e460c Mon Sep 17 00:00:00 2001 From: brandon Date: Tue, 10 Dec 2024 10:46:22 -0800 Subject: [PATCH 11/17] !! Removing client side label checking. Among the various detector modes, most data is valid now --- test/integration/test_groundlight.py | 38 ---------------------------- 1 file changed, 38 deletions(-) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 01a22bc2..c92f2790 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -622,44 +622,6 @@ def test_add_label_names(gl: Groundlight, image_query_yes: ImageQuery, image_que gl.add_label(iqid_no, "NO") gl.add_label(iqid_no, "no") - # Invalid labels - with pytest.raises(ValueError): - gl.add_label(iqid_yes, "PASS") - with pytest.raises(ValueError): - gl.add_label(iqid_yes, "FAIL") - with pytest.raises(ValueError): - gl.add_label(iqid_yes, DeprecatedLabel.PASS) - with pytest.raises(ValueError): - gl.add_label(iqid_yes, DeprecatedLabel.FAIL) - with pytest.raises(ValueError): - gl.add_label(iqid_yes, "sorta") - with pytest.raises(ValueError): - gl.add_label(iqid_yes, "YES ") - with pytest.raises(ValueError): - gl.add_label(iqid_yes, " YES") - with pytest.raises(ValueError): - gl.add_label(iqid_yes, "0") - with pytest.raises(ValueError): - gl.add_label(iqid_yes, "1") - - # We technically don't allow these in the type signature, but users might do it anyway - with pytest.raises(ValueError): - gl.add_label(iqid_yes, 0) # type: ignore - with pytest.raises(ValueError): - gl.add_label(iqid_yes, 1) # type: ignore - with pytest.raises(ValueError): - gl.add_label(iqid_yes, None) # type: ignore - with pytest.raises(ValueError): - gl.add_label(iqid_yes, True) # type: ignore - with pytest.raises(ValueError): - gl.add_label(iqid_yes, False) # type: ignore - with pytest.raises(ValueError): - gl.add_label(iqid_yes, b"YES") # type: ignore - - # We may want to support something like this in the future, but not yet - with pytest.raises(ValueError): - gl.add_label(iqid_yes, Label.UNCLEAR) - def test_label_conversion_produces_strings(): # In our code, it's easier to work with enums, but we allow users to pass in strings or enums From 3309830696e1b988980632068b2d2d876e9cb423 Mon Sep 17 00:00:00 2001 From: Auto-format Bot Date: Tue, 10 Dec 2024 18:47:59 +0000 Subject: [PATCH 12/17] Automatically reformatting code --- test/integration/test_groundlight.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index c92f2790..d5ed922d 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -10,7 +10,7 @@ import pytest from groundlight import Groundlight -from groundlight.binary_labels import VALID_DISPLAY_LABELS, DeprecatedLabel, Label, convert_internal_label_to_display +from groundlight.binary_labels import VALID_DISPLAY_LABELS, Label, convert_internal_label_to_display from groundlight.internalapi import ApiException, InternalApiError, NotFoundError from groundlight.optional_imports import * from groundlight.status_codes import is_user_error From fbf3fbbd81756588742d43926801b2adfbdd27a6 Mon Sep 17 00:00:00 2001 From: brandon Date: Tue, 10 Dec 2024 11:02:03 -0800 Subject: [PATCH 13/17] !! add back in as much type checking as we can now --- src/groundlight/client.py | 7 ++++++- test/integration/test_groundlight.py | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/groundlight/client.py b/src/groundlight/client.py index 41adf1a2..d1d31e08 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -1111,8 +1111,13 @@ def add_label( """ if isinstance(rois, str): raise TypeError("rois must be a list of ROI objects. CLI support is not implemented") - if isinstance(label, int): + + # NOTE: bool is a subclass of int + if type(label) == int: # noqa: E721 label = str(label) + elif not isinstance(label, (str, Label)): + raise TypeError("label must be a string or integer") + if isinstance(image_query, ImageQuery): image_query_id = image_query.id else: diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index c92f2790..01f7956a 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -622,6 +622,16 @@ def test_add_label_names(gl: Groundlight, image_query_yes: ImageQuery, image_que gl.add_label(iqid_no, "NO") gl.add_label(iqid_no, "no") + with pytest.raises(TypeError): + gl.add_label(iqid_yes, None) # type: ignore + with pytest.raises(TypeError): + import IPython; IPython.embed() # type: ignore + gl.add_label(iqid_yes, True) # type: ignore + with pytest.raises(TypeError): + gl.add_label(iqid_yes, False) # type: ignore + with pytest.raises(TypeError): + gl.add_label(iqid_yes, b"YES") # type: ignore + def test_label_conversion_produces_strings(): # In our code, it's easier to work with enums, but we allow users to pass in strings or enums From 9b07cf56457384046cd43bb9680851e5a03afb5e Mon Sep 17 00:00:00 2001 From: Auto-format Bot Date: Tue, 10 Dec 2024 19:03:20 +0000 Subject: [PATCH 14/17] Automatically reformatting code --- src/groundlight/client.py | 2 +- test/integration/test_groundlight.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/groundlight/client.py b/src/groundlight/client.py index d1d31e08..2363b6a5 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -1113,7 +1113,7 @@ def add_label( raise TypeError("rois must be a list of ROI objects. CLI support is not implemented") # NOTE: bool is a subclass of int - if type(label) == int: # noqa: E721 + if type(label) == int: # noqa: E721 label = str(label) elif not isinstance(label, (str, Label)): raise TypeError("label must be a string or integer") diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 7b163c61..2fe79d15 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -625,7 +625,9 @@ def test_add_label_names(gl: Groundlight, image_query_yes: ImageQuery, image_que with pytest.raises(TypeError): gl.add_label(iqid_yes, None) # type: ignore with pytest.raises(TypeError): - import IPython; IPython.embed() # type: ignore + import IPython + + IPython.embed() # type: ignore gl.add_label(iqid_yes, True) # type: ignore with pytest.raises(TypeError): gl.add_label(iqid_yes, False) # type: ignore From 1e9ac4eb7d600dbc082719da5acdf19f5ef15e13 Mon Sep 17 00:00:00 2001 From: brandon Date: Tue, 10 Dec 2024 11:05:29 -0800 Subject: [PATCH 15/17] remove debugging line --- test/integration/test_groundlight.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 7b163c61..ab82f0e7 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -625,7 +625,6 @@ def test_add_label_names(gl: Groundlight, image_query_yes: ImageQuery, image_que with pytest.raises(TypeError): gl.add_label(iqid_yes, None) # type: ignore with pytest.raises(TypeError): - import IPython; IPython.embed() # type: ignore gl.add_label(iqid_yes, True) # type: ignore with pytest.raises(TypeError): gl.add_label(iqid_yes, False) # type: ignore From 790dcdd238f99cf928c6b4f07902b273bfa71b61 Mon Sep 17 00:00:00 2001 From: brandon Date: Tue, 10 Dec 2024 11:25:05 -0800 Subject: [PATCH 16/17] linter suggestions --- src/groundlight/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/groundlight/client.py b/src/groundlight/client.py index 2363b6a5..61c5052c 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -1113,7 +1113,7 @@ def add_label( raise TypeError("rois must be a list of ROI objects. CLI support is not implemented") # NOTE: bool is a subclass of int - if type(label) == int: # noqa: E721 + if type(label) == int: # noqa: E721 pylint: disable=unidiomatic-typecheck label = str(label) elif not isinstance(label, (str, Label)): raise TypeError("label must be a string or integer") From ed3fe0cccfaf343b82b655acfe7b7d53621f1917 Mon Sep 17 00:00:00 2001 From: Auto-format Bot Date: Tue, 10 Dec 2024 19:26:00 +0000 Subject: [PATCH 17/17] Automatically reformatting code --- src/groundlight/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/groundlight/client.py b/src/groundlight/client.py index 61c5052c..9cd4107d 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -1113,7 +1113,7 @@ def add_label( raise TypeError("rois must be a list of ROI objects. CLI support is not implemented") # NOTE: bool is a subclass of int - if type(label) == int: # noqa: E721 pylint: disable=unidiomatic-typecheck + if type(label) == int: # noqa: E721 pylint: disable=unidiomatic-typecheck label = str(label) elif not isinstance(label, (str, Label)): raise TypeError("label must be a string or integer")