diff --git a/generated/.openapi-generator/FILES b/generated/.openapi-generator/FILES index 07ccb77c..302da6ed 100644 --- a/generated/.openapi-generator/FILES +++ b/generated/.openapi-generator/FILES @@ -23,6 +23,7 @@ docs/DetectorCreationInputRequest.md docs/DetectorGroup.md docs/DetectorGroupRequest.md docs/DetectorGroupsApi.md +docs/DetectorModeEnum.md docs/DetectorResetApi.md docs/DetectorTypeEnum.md docs/DetectorsApi.md @@ -50,6 +51,7 @@ docs/MultiClassificationResult.md docs/Note.md docs/NoteRequest.md docs/NotesApi.md +docs/NullEnum.md docs/PaginatedDetectorList.md docs/PaginatedImageQueryList.md docs/PaginatedMLPipelineList.md @@ -116,6 +118,7 @@ groundlight_openapi_client/model/detector.py groundlight_openapi_client/model/detector_creation_input_request.py groundlight_openapi_client/model/detector_group.py groundlight_openapi_client/model/detector_group_request.py +groundlight_openapi_client/model/detector_mode_enum.py groundlight_openapi_client/model/detector_type_enum.py groundlight_openapi_client/model/edge_model_info.py groundlight_openapi_client/model/escalation_type_enum.py @@ -136,6 +139,7 @@ groundlight_openapi_client/model/multi_class_mode_configuration.py groundlight_openapi_client/model/multi_classification_result.py groundlight_openapi_client/model/note.py groundlight_openapi_client/model/note_request.py +groundlight_openapi_client/model/null_enum.py groundlight_openapi_client/model/paginated_detector_list.py groundlight_openapi_client/model/paginated_image_query_list.py groundlight_openapi_client/model/paginated_ml_pipeline_list.py @@ -168,4 +172,6 @@ setup.cfg setup.py test-requirements.txt test/__init__.py +test/test_detector_mode_enum.py +test/test_null_enum.py tox.ini diff --git a/generated/README.md b/generated/README.md index 56299a25..40630eba 100644 --- a/generated/README.md +++ b/generated/README.md @@ -171,6 +171,7 @@ Class | Method | HTTP request | Description - [DetectorCreationInputRequest](docs/DetectorCreationInputRequest.md) - [DetectorGroup](docs/DetectorGroup.md) - [DetectorGroupRequest](docs/DetectorGroupRequest.md) + - [DetectorModeEnum](docs/DetectorModeEnum.md) - [DetectorTypeEnum](docs/DetectorTypeEnum.md) - [EdgeModelInfo](docs/EdgeModelInfo.md) - [EscalationTypeEnum](docs/EscalationTypeEnum.md) @@ -191,6 +192,7 @@ Class | Method | HTTP request | Description - [MultiClassificationResult](docs/MultiClassificationResult.md) - [Note](docs/Note.md) - [NoteRequest](docs/NoteRequest.md) + - [NullEnum](docs/NullEnum.md) - [PaginatedDetectorList](docs/PaginatedDetectorList.md) - [PaginatedImageQueryList](docs/PaginatedImageQueryList.md) - [PaginatedMLPipelineList](docs/PaginatedMLPipelineList.md) diff --git a/generated/docs/DetectorModeEnum.md b/generated/docs/DetectorModeEnum.md new file mode 100644 index 00000000..ef0c9037 --- /dev/null +++ b/generated/docs/DetectorModeEnum.md @@ -0,0 +1,12 @@ +# DetectorModeEnum + +* `BINARY` - BINARY * `COUNT` - COUNT * `MULTI_CLASS` - MULTI_CLASS * `TEXT` - TEXT * `BOUNDING_BOX` - BOUNDING_BOX + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**value** | **str** | * `BINARY` - BINARY * `COUNT` - COUNT * `MULTI_CLASS` - MULTI_CLASS * `TEXT` - TEXT * `BOUNDING_BOX` - BOUNDING_BOX | must be one of ["BINARY", "COUNT", "MULTI_CLASS", "TEXT", "BOUNDING_BOX", ] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/generated/docs/MLPipeline.md b/generated/docs/MLPipeline.md index bdf9270f..b26b72e3 100644 --- a/generated/docs/MLPipeline.md +++ b/generated/docs/MLPipeline.md @@ -1,12 +1,12 @@ # MLPipeline -A single ML pipeline attached to a detector. +A single ML pipeline attached to a detector. Includes training status and metrics for the Pipeline Details UI. ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **id** | **str** | | [readonly] -**pipeline_config** | **str, none_type** | configuration needed to instantiate a prediction pipeline. | [readonly] +**pipeline_config** | **str** | Get the resolved pipeline config, including defaults. | [readonly] **is_active_pipeline** | **bool** | If True, this is the pipeline is used for inference, active learning, etc. for its parent Predictor. | [readonly] **is_edge_pipeline** | **bool** | If True, this pipeline is enabled for edge inference. | [readonly] **is_unclear_pipeline** | **bool** | If True, this pipeline is used to train classifier for human unclear label prediction. | [readonly] @@ -14,6 +14,14 @@ Name | Type | Description | Notes **is_enabled** | **bool** | If False, this pipeline will not be run for any use case. | [readonly] **created_at** | **datetime** | | [readonly] **trained_at** | **datetime, none_type** | | [readonly] +**type** | **str** | Determine the pipeline type (active, shadow, unclear, oodd). | [readonly] +**friendly_name** | **str, none_type** | Get the friendly name from the MLBinary. | [readonly] +**model_binary_id** | **str, none_type** | | +**training_in_progress** | **{str: (bool, date, datetime, dict, float, int, list, str, none_type)}, none_type** | Check if training is currently in progress for this pipeline. | [readonly] +**metrics** | **{str: (bool, date, datetime, dict, float, int, list, str, none_type)}** | Get the metrics for this pipeline from MLBinary metadata and evaluation results. | [readonly] +**eval_mlbinary_key** | **str, none_type** | Get the MLBinary key that was evaluated. | [readonly] +**eval_mlbinary_revision_number** | **int, none_type** | Get the revision number of the MLBinary that was evaluated. | [readonly] +**eval_mlbinary_friendly_name** | **str, none_type** | Get the friendly name of the MLBinary that was evaluated. | [readonly] **any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/generated/docs/PrimingGroup.md b/generated/docs/PrimingGroup.md index 42146c3a..823fe601 100644 --- a/generated/docs/PrimingGroup.md +++ b/generated/docs/PrimingGroup.md @@ -7,8 +7,10 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **id** | **str** | | [readonly] **name** | **str** | | +**num_classes** | **int, none_type** | Number of output classes this priming group is trained for. Set automatically from the source predictor when created via create_user_priming_group (MULTI_CLASS only). Nullable for legacy rows and for modes where class count isn't meaningful; a follow-up PR will tighten this after manual backfill. | [readonly] **is_global** | **bool** | If True, this priming group is shared to all Groundlight users. Can only be set by Groundlight admins. | [readonly] **created_at** | **datetime** | | [readonly] +**detector_mode** | **bool, date, datetime, dict, float, int, list, str, none_type** | Detector mode this priming group is intended for (BINARY, MULTI_CLASS, etc.). Validated against the pipeline_config of every active and shadow base MLBinary in clean(). Nullable only for legacy rows pre-dating this field; new PrimingGroups must set it. * `BINARY` - BINARY * `COUNT` - COUNT * `MULTI_CLASS` - MULTI_CLASS * `TEXT` - TEXT * `BOUNDING_BOX` - BOUNDING_BOX | [optional] **canonical_query** | **str, none_type** | Canonical semantic query for this priming group | [optional] **active_pipeline_config** | **str, none_type** | Active pipeline config override for new detectors created in this priming group. If set, this overrides the default active pipeline config at creation time.Can be either a pipeline name or full config string. | [optional] **priming_group_specific_shadow_pipeline_configs** | **bool, date, datetime, dict, float, int, list, str, none_type** | Configs for shadow pipelines to create for detectors in this priming group. These are added to the default shadow pipeline configs for a detector of the given mode. Each entry is either a pipeline name or full config string. | [optional] diff --git a/generated/docs/PrimingGroupCreationInputRequest.md b/generated/docs/PrimingGroupCreationInputRequest.md index e92668cb..7c310871 100644 --- a/generated/docs/PrimingGroupCreationInputRequest.md +++ b/generated/docs/PrimingGroupCreationInputRequest.md @@ -7,6 +7,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **name** | **str** | Name for the new priming group. | **source_ml_pipeline_id** | **str** | ID of an MLPipeline owned by this account whose trained model will seed the priming group. | +**detector_mode** | **bool, date, datetime, dict, float, int, list, str, none_type** | Detector mode this priming group is intended for (BINARY, MULTI_CLASS, etc.). Must match the mode supported by the source MLPipeline's pipeline_config. * `BINARY` - BINARY * `COUNT` - COUNT * `MULTI_CLASS` - MULTI_CLASS * `TEXT` - TEXT * `BOUNDING_BOX` - BOUNDING_BOX | **canonical_query** | **str, none_type** | Optional canonical semantic query describing this priming group. | [optional] **disable_shadow_pipelines** | **bool** | If true, new detectors added to this priming group will not receive the default shadow pipelines. This guarantees the primed active model is never switched off. | [optional] if omitted the server will use the default value of False **any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional] diff --git a/generated/docs/PrimingGroupsApi.md b/generated/docs/PrimingGroupsApi.md index 36c853c0..7cf7c81b 100644 --- a/generated/docs/PrimingGroupsApi.md +++ b/generated/docs/PrimingGroupsApi.md @@ -52,6 +52,7 @@ with groundlight_openapi_client.ApiClient(configuration) as api_client: priming_group_creation_input_request = PrimingGroupCreationInputRequest( name="name_example", source_ml_pipeline_id="source_ml_pipeline_id_example", + detector_mode=None, canonical_query="canonical_query_example", disable_shadow_pipelines=False, ) # PrimingGroupCreationInputRequest | diff --git a/generated/groundlight_openapi_client/model/detector_mode_enum.py b/generated/groundlight_openapi_client/model/detector_mode_enum.py new file mode 100644 index 00000000..98dc4f0b --- /dev/null +++ b/generated/groundlight_openapi_client/model/detector_mode_enum.py @@ -0,0 +1,286 @@ +""" + Groundlight API + + Groundlight makes it simple to understand images. You can easily create computer vision detectors just by describing what you want to know using natural language. # noqa: E501 + + The version of the OpenAPI document: 0.18.2 + Contact: support@groundlight.ai + Generated by: https://openapi-generator.tech +""" + +import re # noqa: F401 +import sys # noqa: F401 + +from groundlight_openapi_client.model_utils import ( # noqa: F401 + ApiTypeError, + ModelComposed, + ModelNormal, + ModelSimple, + cached_property, + change_keys_js_to_python, + convert_js_args_to_python_args, + date, + datetime, + file_type, + none_type, + validate_get_composed_info, + OpenApiModel, +) +from groundlight_openapi_client.exceptions import ApiAttributeError + + +class DetectorModeEnum(ModelSimple): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + + Attributes: + allowed_values (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + with a capitalized key describing the allowed value and an allowed + value. These dicts store the allowed enum values. + validations (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + that stores validations for max_length, min_length, max_items, + min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum, + inclusive_minimum, and regex. + additional_properties_type (tuple): A tuple of classes accepted + as additional properties values. + """ + + allowed_values = { + ("value",): { + "BINARY": "BINARY", + "COUNT": "COUNT", + "MULTI_CLASS": "MULTI_CLASS", + "TEXT": "TEXT", + "BOUNDING_BOX": "BOUNDING_BOX", + }, + } + + validations = {} + + additional_properties_type = None + + _nullable = False + + @cached_property + def openapi_types(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + + Returns + openapi_types (dict): The key is attribute name + and the value is attribute type. + """ + return { + "value": (str,), + } + + @cached_property + def discriminator(): + return None + + attribute_map = {} + + read_only_vars = set() + + _composed_schemas = None + + required_properties = set([ + "_data_store", + "_check_type", + "_spec_property_naming", + "_path_to_item", + "_configuration", + "_visited_composed_classes", + ]) + + @convert_js_args_to_python_args + def __init__(self, *args, **kwargs): + """DetectorModeEnum - a model defined in OpenAPI + + Note that value can be passed either in args or in kwargs, but not in both. + + Args: + args[0] (str): * `BINARY` - BINARY * `COUNT` - COUNT * `MULTI_CLASS` - MULTI_CLASS * `TEXT` - TEXT * `BOUNDING_BOX` - BOUNDING_BOX., must be one of ["BINARY", "COUNT", "MULTI_CLASS", "TEXT", "BOUNDING_BOX", ] # noqa: E501 + + Keyword Args: + value (str): * `BINARY` - BINARY * `COUNT` - COUNT * `MULTI_CLASS` - MULTI_CLASS * `TEXT` - TEXT * `BOUNDING_BOX` - BOUNDING_BOX., must be one of ["BINARY", "COUNT", "MULTI_CLASS", "TEXT", "BOUNDING_BOX", ] # noqa: E501 + _check_type (bool): if True, values for parameters in openapi_types + will be type checked and a TypeError will be + raised if the wrong type is input. + Defaults to True + _path_to_item (tuple/list): This is a list of keys or values to + drill down to the model in received_data + when deserializing a response + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _configuration (Configuration): the instance to use when + deserializing a file_type parameter. + If passed, type conversion is attempted + If omitted no type conversion is done. + _visited_composed_classes (tuple): This stores a tuple of + classes that we have traveled through so that + if we see that class again we will not use its + discriminator again. + When traveling through a discriminator, the + composed schema that is + is traveled through is added to this set. + For example if Animal has a discriminator + petType and we pass in "Dog", and the class Dog + allOf includes Animal, we move through Animal + once using the discriminator, and pick Dog. + Then in Dog, we will make an instance of the + Animal class but this time we won't travel + through its discriminator because we passed in + _visited_composed_classes = (Animal,) + """ + # required up here when default value is not given + _path_to_item = kwargs.pop("_path_to_item", ()) + + if "value" in kwargs: + value = kwargs.pop("value") + elif args: + args = list(args) + value = args.pop(0) + else: + raise ApiTypeError( + "value is required, but not passed in args or kwargs and doesn't have default", + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _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." + % ( + args, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + self.value = value + if kwargs: + raise ApiTypeError( + "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." + % ( + kwargs, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + @classmethod + @convert_js_args_to_python_args + def _from_openapi_data(cls, *args, **kwargs): + """DetectorModeEnum - a model defined in OpenAPI + + Note that value can be passed either in args or in kwargs, but not in both. + + Args: + args[0] (str): * `BINARY` - BINARY * `COUNT` - COUNT * `MULTI_CLASS` - MULTI_CLASS * `TEXT` - TEXT * `BOUNDING_BOX` - BOUNDING_BOX., must be one of ["BINARY", "COUNT", "MULTI_CLASS", "TEXT", "BOUNDING_BOX", ] # noqa: E501 + + Keyword Args: + value (str): * `BINARY` - BINARY * `COUNT` - COUNT * `MULTI_CLASS` - MULTI_CLASS * `TEXT` - TEXT * `BOUNDING_BOX` - BOUNDING_BOX., must be one of ["BINARY", "COUNT", "MULTI_CLASS", "TEXT", "BOUNDING_BOX", ] # noqa: E501 + _check_type (bool): if True, values for parameters in openapi_types + will be type checked and a TypeError will be + raised if the wrong type is input. + Defaults to True + _path_to_item (tuple/list): This is a list of keys or values to + drill down to the model in received_data + when deserializing a response + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _configuration (Configuration): the instance to use when + deserializing a file_type parameter. + If passed, type conversion is attempted + If omitted no type conversion is done. + _visited_composed_classes (tuple): This stores a tuple of + classes that we have traveled through so that + if we see that class again we will not use its + discriminator again. + When traveling through a discriminator, the + composed schema that is + is traveled through is added to this set. + For example if Animal has a discriminator + petType and we pass in "Dog", and the class Dog + allOf includes Animal, we move through Animal + once using the discriminator, and pick Dog. + Then in Dog, we will make an instance of the + Animal class but this time we won't travel + through its discriminator because we passed in + _visited_composed_classes = (Animal,) + """ + # required up here when default value is not given + _path_to_item = kwargs.pop("_path_to_item", ()) + + self = super(OpenApiModel, cls).__new__(cls) + + if "value" in kwargs: + value = kwargs.pop("value") + elif args: + args = list(args) + value = args.pop(0) + else: + raise ApiTypeError( + "value is required, but not passed in args or kwargs and doesn't have default", + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _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." + % ( + args, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + self.value = value + if kwargs: + raise ApiTypeError( + "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." + % ( + kwargs, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + return self diff --git a/generated/groundlight_openapi_client/model/ml_pipeline.py b/generated/groundlight_openapi_client/model/ml_pipeline.py index 5737e1d6..621a87ba 100644 --- a/generated/groundlight_openapi_client/model/ml_pipeline.py +++ b/generated/groundlight_openapi_client/model/ml_pipeline.py @@ -89,10 +89,7 @@ def openapi_types(): """ return { "id": (str,), # noqa: E501 - "pipeline_config": ( - str, - none_type, - ), # noqa: E501 + "pipeline_config": (str,), # noqa: E501 "is_active_pipeline": (bool,), # noqa: E501 "is_edge_pipeline": (bool,), # noqa: E501 "is_unclear_pipeline": (bool,), # noqa: E501 @@ -103,6 +100,32 @@ def openapi_types(): datetime, none_type, ), # noqa: E501 + "type": (str,), # noqa: E501 + "friendly_name": ( + str, + none_type, + ), # noqa: E501 + "model_binary_id": ( + str, + none_type, + ), # noqa: E501 + "training_in_progress": ( + {str: (bool, date, datetime, dict, float, int, list, str, none_type)}, + none_type, + ), # noqa: E501 + "metrics": ({str: (bool, date, datetime, dict, float, int, list, str, none_type)},), # noqa: E501 + "eval_mlbinary_key": ( + str, + none_type, + ), # noqa: E501 + "eval_mlbinary_revision_number": ( + int, + none_type, + ), # noqa: E501 + "eval_mlbinary_friendly_name": ( + str, + none_type, + ), # noqa: E501 } @cached_property @@ -119,6 +142,14 @@ def discriminator(): "is_enabled": "is_enabled", # noqa: E501 "created_at": "created_at", # noqa: E501 "trained_at": "trained_at", # noqa: E501 + "type": "type", # noqa: E501 + "friendly_name": "friendly_name", # noqa: E501 + "model_binary_id": "model_binary_id", # noqa: E501 + "training_in_progress": "training_in_progress", # noqa: E501 + "metrics": "metrics", # noqa: E501 + "eval_mlbinary_key": "eval_mlbinary_key", # noqa: E501 + "eval_mlbinary_revision_number": "eval_mlbinary_revision_number", # noqa: E501 + "eval_mlbinary_friendly_name": "eval_mlbinary_friendly_name", # noqa: E501 } read_only_vars = { @@ -131,6 +162,13 @@ def discriminator(): "is_enabled", # noqa: E501 "created_at", # noqa: E501 "trained_at", # noqa: E501 + "type", # noqa: E501 + "friendly_name", # noqa: E501 + "training_in_progress", # noqa: E501 + "metrics", # noqa: E501 + "eval_mlbinary_key", # noqa: E501 + "eval_mlbinary_revision_number", # noqa: E501 + "eval_mlbinary_friendly_name", # noqa: E501 } _composed_schemas = {} @@ -148,6 +186,14 @@ def _from_openapi_data( is_enabled, created_at, trained_at, + type, + friendly_name, + model_binary_id, + training_in_progress, + metrics, + eval_mlbinary_key, + eval_mlbinary_revision_number, + eval_mlbinary_friendly_name, *args, **kwargs, ): # noqa: E501 @@ -155,7 +201,7 @@ def _from_openapi_data( Args: id (str): - pipeline_config (str, none_type): configuration needed to instantiate a prediction pipeline. + pipeline_config (str): Get the resolved pipeline config, including defaults. is_active_pipeline (bool): If True, this is the pipeline is used for inference, active learning, etc. for its parent Predictor. is_edge_pipeline (bool): If True, this pipeline is enabled for edge inference. is_unclear_pipeline (bool): If True, this pipeline is used to train classifier for human unclear label prediction. @@ -163,6 +209,14 @@ def _from_openapi_data( is_enabled (bool): If False, this pipeline will not be run for any use case. created_at (datetime): trained_at (datetime, none_type): + type (str): Determine the pipeline type (active, shadow, unclear, oodd). + friendly_name (str, none_type): Get the friendly name from the MLBinary. + model_binary_id (str, none_type): + training_in_progress ({str: (bool, date, datetime, dict, float, int, list, str, none_type)}, none_type): Check if training is currently in progress for this pipeline. + metrics ({str: (bool, date, datetime, dict, float, int, list, str, none_type)}): Get the metrics for this pipeline from MLBinary metadata and evaluation results. + eval_mlbinary_key (str, none_type): Get the MLBinary key that was evaluated. + eval_mlbinary_revision_number (int, none_type): Get the revision number of the MLBinary that was evaluated. + eval_mlbinary_friendly_name (str, none_type): Get the friendly name of the MLBinary that was evaluated. Keyword Args: _check_type (bool): if True, values for parameters in openapi_types @@ -232,6 +286,14 @@ def _from_openapi_data( self.is_enabled = is_enabled self.created_at = created_at self.trained_at = trained_at + self.type = type + self.friendly_name = friendly_name + self.model_binary_id = model_binary_id + self.training_in_progress = training_in_progress + self.metrics = metrics + self.eval_mlbinary_key = eval_mlbinary_key + self.eval_mlbinary_revision_number = eval_mlbinary_revision_number + self.eval_mlbinary_friendly_name = eval_mlbinary_friendly_name for var_name, var_value in kwargs.items(): if ( var_name not in self.attribute_map @@ -254,9 +316,10 @@ def _from_openapi_data( ]) @convert_js_args_to_python_args - def __init__(self, *args, **kwargs): # noqa: E501 + def __init__(self, model_binary_id, *args, **kwargs): # noqa: E501 """MLPipeline - a model defined in OpenAPI + model_binary_id (str, none_type): Keyword Args: _check_type (bool): if True, values for parameters in openapi_types will be type checked and a TypeError will be @@ -314,6 +377,7 @@ def __init__(self, *args, **kwargs): # noqa: E501 self._configuration = _configuration self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + self.model_binary_id = model_binary_id for var_name, var_value in kwargs.items(): if ( var_name not in self.attribute_map diff --git a/generated/groundlight_openapi_client/model/null_enum.py b/generated/groundlight_openapi_client/model/null_enum.py new file mode 100644 index 00000000..c7e7cfab --- /dev/null +++ b/generated/groundlight_openapi_client/model/null_enum.py @@ -0,0 +1,282 @@ +""" + Groundlight API + + Groundlight makes it simple to understand images. You can easily create computer vision detectors just by describing what you want to know using natural language. # noqa: E501 + + The version of the OpenAPI document: 0.18.2 + Contact: support@groundlight.ai + Generated by: https://openapi-generator.tech +""" + +import re # noqa: F401 +import sys # noqa: F401 + +from groundlight_openapi_client.model_utils import ( # noqa: F401 + ApiTypeError, + ModelComposed, + ModelNormal, + ModelSimple, + cached_property, + change_keys_js_to_python, + convert_js_args_to_python_args, + date, + datetime, + file_type, + none_type, + validate_get_composed_info, + OpenApiModel, +) +from groundlight_openapi_client.exceptions import ApiAttributeError + + +class NullEnum(ModelSimple): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + + Attributes: + allowed_values (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + with a capitalized key describing the allowed value and an allowed + value. These dicts store the allowed enum values. + validations (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + that stores validations for max_length, min_length, max_items, + min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum, + inclusive_minimum, and regex. + additional_properties_type (tuple): A tuple of classes accepted + as additional properties values. + """ + + allowed_values = { + ("value",): { + "NULL": "null", + }, + } + + validations = {} + + additional_properties_type = None + + _nullable = False + + @cached_property + def openapi_types(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + + Returns + openapi_types (dict): The key is attribute name + and the value is attribute type. + """ + return { + "value": (str,), + } + + @cached_property + def discriminator(): + return None + + attribute_map = {} + + read_only_vars = set() + + _composed_schemas = None + + required_properties = set([ + "_data_store", + "_check_type", + "_spec_property_naming", + "_path_to_item", + "_configuration", + "_visited_composed_classes", + ]) + + @convert_js_args_to_python_args + def __init__(self, *args, **kwargs): + """NullEnum - a model defined in OpenAPI + + Note that value can be passed either in args or in kwargs, but not in both. + + Args: + args[0] (str):, must be one of ["null", ] # noqa: E501 + + Keyword Args: + value (str):, must be one of ["null", ] # noqa: E501 + _check_type (bool): if True, values for parameters in openapi_types + will be type checked and a TypeError will be + raised if the wrong type is input. + Defaults to True + _path_to_item (tuple/list): This is a list of keys or values to + drill down to the model in received_data + when deserializing a response + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _configuration (Configuration): the instance to use when + deserializing a file_type parameter. + If passed, type conversion is attempted + If omitted no type conversion is done. + _visited_composed_classes (tuple): This stores a tuple of + classes that we have traveled through so that + if we see that class again we will not use its + discriminator again. + When traveling through a discriminator, the + composed schema that is + is traveled through is added to this set. + For example if Animal has a discriminator + petType and we pass in "Dog", and the class Dog + allOf includes Animal, we move through Animal + once using the discriminator, and pick Dog. + Then in Dog, we will make an instance of the + Animal class but this time we won't travel + through its discriminator because we passed in + _visited_composed_classes = (Animal,) + """ + # required up here when default value is not given + _path_to_item = kwargs.pop("_path_to_item", ()) + + if "value" in kwargs: + value = kwargs.pop("value") + elif args: + args = list(args) + value = args.pop(0) + else: + raise ApiTypeError( + "value is required, but not passed in args or kwargs and doesn't have default", + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _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." + % ( + args, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + self.value = value + if kwargs: + raise ApiTypeError( + "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." + % ( + kwargs, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + @classmethod + @convert_js_args_to_python_args + def _from_openapi_data(cls, *args, **kwargs): + """NullEnum - a model defined in OpenAPI + + Note that value can be passed either in args or in kwargs, but not in both. + + Args: + args[0] (str):, must be one of ["null", ] # noqa: E501 + + Keyword Args: + value (str):, must be one of ["null", ] # noqa: E501 + _check_type (bool): if True, values for parameters in openapi_types + will be type checked and a TypeError will be + raised if the wrong type is input. + Defaults to True + _path_to_item (tuple/list): This is a list of keys or values to + drill down to the model in received_data + when deserializing a response + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _configuration (Configuration): the instance to use when + deserializing a file_type parameter. + If passed, type conversion is attempted + If omitted no type conversion is done. + _visited_composed_classes (tuple): This stores a tuple of + classes that we have traveled through so that + if we see that class again we will not use its + discriminator again. + When traveling through a discriminator, the + composed schema that is + is traveled through is added to this set. + For example if Animal has a discriminator + petType and we pass in "Dog", and the class Dog + allOf includes Animal, we move through Animal + once using the discriminator, and pick Dog. + Then in Dog, we will make an instance of the + Animal class but this time we won't travel + through its discriminator because we passed in + _visited_composed_classes = (Animal,) + """ + # required up here when default value is not given + _path_to_item = kwargs.pop("_path_to_item", ()) + + self = super(OpenApiModel, cls).__new__(cls) + + if "value" in kwargs: + value = kwargs.pop("value") + elif args: + args = list(args) + value = args.pop(0) + else: + raise ApiTypeError( + "value is required, but not passed in args or kwargs and doesn't have default", + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _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." + % ( + args, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + self.value = value + if kwargs: + raise ApiTypeError( + "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." + % ( + kwargs, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + return self diff --git a/generated/groundlight_openapi_client/model/priming_group.py b/generated/groundlight_openapi_client/model/priming_group.py index 023c0d66..8f762e6d 100644 --- a/generated/groundlight_openapi_client/model/priming_group.py +++ b/generated/groundlight_openapi_client/model/priming_group.py @@ -29,6 +29,16 @@ from groundlight_openapi_client.exceptions import ApiAttributeError +def lazy_import(): + from groundlight_openapi_client.model.blank_enum import BlankEnum + from groundlight_openapi_client.model.detector_mode_enum import DetectorModeEnum + from groundlight_openapi_client.model.null_enum import NullEnum + + globals()["BlankEnum"] = BlankEnum + globals()["DetectorModeEnum"] = DetectorModeEnum + globals()["NullEnum"] = NullEnum + + class PrimingGroup(ModelNormal): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -73,6 +83,7 @@ def additional_properties_type(): This must be a method because a model may have properties that are of type self, this must run after the class is loaded """ + lazy_import() return ( bool, date, @@ -97,11 +108,27 @@ def openapi_types(): openapi_types (dict): The key is attribute name and the value is attribute type. """ + lazy_import() return { "id": (str,), # noqa: E501 "name": (str,), # noqa: E501 + "num_classes": ( + int, + none_type, + ), # noqa: E501 "is_global": (bool,), # noqa: E501 "created_at": (datetime,), # noqa: E501 + "detector_mode": ( + bool, + date, + datetime, + dict, + float, + int, + list, + str, + none_type, + ), # noqa: E501 "canonical_query": ( str, none_type, @@ -131,8 +158,10 @@ def discriminator(): attribute_map = { "id": "id", # noqa: E501 "name": "name", # noqa: E501 + "num_classes": "num_classes", # noqa: E501 "is_global": "is_global", # noqa: E501 "created_at": "created_at", # noqa: E501 + "detector_mode": "detector_mode", # noqa: E501 "canonical_query": "canonical_query", # noqa: E501 "active_pipeline_config": "active_pipeline_config", # noqa: E501 "priming_group_specific_shadow_pipeline_configs": ( @@ -143,6 +172,7 @@ def discriminator(): read_only_vars = { "id", # noqa: E501 + "num_classes", # noqa: E501 "is_global", # noqa: E501 "created_at", # noqa: E501 } @@ -151,12 +181,13 @@ def discriminator(): @classmethod @convert_js_args_to_python_args - def _from_openapi_data(cls, id, name, is_global, created_at, *args, **kwargs): # noqa: E501 + def _from_openapi_data(cls, id, name, num_classes, is_global, created_at, *args, **kwargs): # noqa: E501 """PrimingGroup - a model defined in OpenAPI Args: id (str): name (str): + num_classes (int, none_type): Number of output classes this priming group is trained for. Set automatically from the source predictor when created via create_user_priming_group (MULTI_CLASS only). Nullable for legacy rows and for modes where class count isn't meaningful; a follow-up PR will tighten this after manual backfill. is_global (bool): If True, this priming group is shared to all Groundlight users. Can only be set by Groundlight admins. created_at (datetime): @@ -191,6 +222,7 @@ def _from_openapi_data(cls, id, name, is_global, created_at, *args, **kwargs): Animal class but this time we won't travel through its discriminator because we passed in _visited_composed_classes = (Animal,) + detector_mode (bool, date, datetime, dict, float, int, list, str, none_type): Detector mode this priming group is intended for (BINARY, MULTI_CLASS, etc.). Validated against the pipeline_config of every active and shadow base MLBinary in clean(). Nullable only for legacy rows pre-dating this field; new PrimingGroups must set it. * `BINARY` - BINARY * `COUNT` - COUNT * `MULTI_CLASS` - MULTI_CLASS * `TEXT` - TEXT * `BOUNDING_BOX` - BOUNDING_BOX. [optional] # noqa: E501 canonical_query (str, none_type): Canonical semantic query for this priming group. [optional] # noqa: E501 active_pipeline_config (str, none_type): Active pipeline config override for new detectors created in this priming group. If set, this overrides the default active pipeline config at creation time.Can be either a pipeline name or full config string.. [optional] # noqa: E501 priming_group_specific_shadow_pipeline_configs (bool, date, datetime, dict, float, int, list, str, none_type): Configs for shadow pipelines to create for detectors in this priming group. These are added to the default shadow pipeline configs for a detector of the given mode. Each entry is either a pipeline name or full config string. . [optional] # noqa: E501 @@ -225,6 +257,7 @@ def _from_openapi_data(cls, id, name, is_global, created_at, *args, **kwargs): self.id = id self.name = name + self.num_classes = num_classes self.is_global = is_global self.created_at = created_at for var_name, var_value in kwargs.items(): @@ -284,6 +317,7 @@ def __init__(self, name, *args, **kwargs): # noqa: E501 Animal class but this time we won't travel through its discriminator because we passed in _visited_composed_classes = (Animal,) + detector_mode (bool, date, datetime, dict, float, int, list, str, none_type): Detector mode this priming group is intended for (BINARY, MULTI_CLASS, etc.). Validated against the pipeline_config of every active and shadow base MLBinary in clean(). Nullable only for legacy rows pre-dating this field; new PrimingGroups must set it. * `BINARY` - BINARY * `COUNT` - COUNT * `MULTI_CLASS` - MULTI_CLASS * `TEXT` - TEXT * `BOUNDING_BOX` - BOUNDING_BOX. [optional] # noqa: E501 canonical_query (str, none_type): Canonical semantic query for this priming group. [optional] # noqa: E501 active_pipeline_config (str, none_type): Active pipeline config override for new detectors created in this priming group. If set, this overrides the default active pipeline config at creation time.Can be either a pipeline name or full config string.. [optional] # noqa: E501 priming_group_specific_shadow_pipeline_configs (bool, date, datetime, dict, float, int, list, str, none_type): Configs for shadow pipelines to create for detectors in this priming group. These are added to the default shadow pipeline configs for a detector of the given mode. Each entry is either a pipeline name or full config string. . [optional] # noqa: E501 diff --git a/generated/groundlight_openapi_client/model/priming_group_creation_input_request.py b/generated/groundlight_openapi_client/model/priming_group_creation_input_request.py index f84e96b1..3eb09a40 100644 --- a/generated/groundlight_openapi_client/model/priming_group_creation_input_request.py +++ b/generated/groundlight_openapi_client/model/priming_group_creation_input_request.py @@ -29,6 +29,12 @@ from groundlight_openapi_client.exceptions import ApiAttributeError +def lazy_import(): + from groundlight_openapi_client.model.detector_mode_enum import DetectorModeEnum + + globals()["DetectorModeEnum"] = DetectorModeEnum + + class PrimingGroupCreationInputRequest(ModelNormal): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -75,6 +81,7 @@ def additional_properties_type(): This must be a method because a model may have properties that are of type self, this must run after the class is loaded """ + lazy_import() return ( bool, date, @@ -99,9 +106,21 @@ def openapi_types(): openapi_types (dict): The key is attribute name and the value is attribute type. """ + lazy_import() return { "name": (str,), # noqa: E501 "source_ml_pipeline_id": (str,), # noqa: E501 + "detector_mode": ( + bool, + date, + datetime, + dict, + float, + int, + list, + str, + none_type, + ), # noqa: E501 "canonical_query": ( str, none_type, @@ -116,6 +135,7 @@ def discriminator(): attribute_map = { "name": "name", # noqa: E501 "source_ml_pipeline_id": "source_ml_pipeline_id", # noqa: E501 + "detector_mode": "detector_mode", # noqa: E501 "canonical_query": "canonical_query", # noqa: E501 "disable_shadow_pipelines": "disable_shadow_pipelines", # noqa: E501 } @@ -126,12 +146,13 @@ def discriminator(): @classmethod @convert_js_args_to_python_args - def _from_openapi_data(cls, name, source_ml_pipeline_id, *args, **kwargs): # noqa: E501 + def _from_openapi_data(cls, name, source_ml_pipeline_id, detector_mode, *args, **kwargs): # noqa: E501 """PrimingGroupCreationInputRequest - a model defined in OpenAPI Args: name (str): Name for the new priming group. source_ml_pipeline_id (str): ID of an MLPipeline owned by this account whose trained model will seed the priming group. + detector_mode (bool, date, datetime, dict, float, int, list, str, none_type): Detector mode this priming group is intended for (BINARY, MULTI_CLASS, etc.). Must match the mode supported by the source MLPipeline's pipeline_config. * `BINARY` - BINARY * `COUNT` - COUNT * `MULTI_CLASS` - MULTI_CLASS * `TEXT` - TEXT * `BOUNDING_BOX` - BOUNDING_BOX Keyword Args: _check_type (bool): if True, values for parameters in openapi_types @@ -196,6 +217,7 @@ def _from_openapi_data(cls, name, source_ml_pipeline_id, *args, **kwargs): # no self.name = name self.source_ml_pipeline_id = source_ml_pipeline_id + self.detector_mode = detector_mode for var_name, var_value in kwargs.items(): if ( var_name not in self.attribute_map @@ -218,12 +240,13 @@ def _from_openapi_data(cls, name, source_ml_pipeline_id, *args, **kwargs): # no ]) @convert_js_args_to_python_args - def __init__(self, name, source_ml_pipeline_id, *args, **kwargs): # noqa: E501 + def __init__(self, name, source_ml_pipeline_id, detector_mode, *args, **kwargs): # noqa: E501 """PrimingGroupCreationInputRequest - a model defined in OpenAPI Args: name (str): Name for the new priming group. source_ml_pipeline_id (str): ID of an MLPipeline owned by this account whose trained model will seed the priming group. + detector_mode (bool, date, datetime, dict, float, int, list, str, none_type): Detector mode this priming group is intended for (BINARY, MULTI_CLASS, etc.). Must match the mode supported by the source MLPipeline's pipeline_config. * `BINARY` - BINARY * `COUNT` - COUNT * `MULTI_CLASS` - MULTI_CLASS * `TEXT` - TEXT * `BOUNDING_BOX` - BOUNDING_BOX Keyword Args: _check_type (bool): if True, values for parameters in openapi_types @@ -286,6 +309,7 @@ def __init__(self, name, source_ml_pipeline_id, *args, **kwargs): # noqa: E501 self.name = name self.source_ml_pipeline_id = source_ml_pipeline_id + self.detector_mode = detector_mode for var_name, var_value in kwargs.items(): if ( var_name not in self.attribute_map diff --git a/generated/groundlight_openapi_client/models/__init__.py b/generated/groundlight_openapi_client/models/__init__.py index 5ba5bfa2..f35cb11e 100644 --- a/generated/groundlight_openapi_client/models/__init__.py +++ b/generated/groundlight_openapi_client/models/__init__.py @@ -30,6 +30,7 @@ from groundlight_openapi_client.model.detector_creation_input_request import DetectorCreationInputRequest from groundlight_openapi_client.model.detector_group import DetectorGroup from groundlight_openapi_client.model.detector_group_request import DetectorGroupRequest +from groundlight_openapi_client.model.detector_mode_enum import DetectorModeEnum from groundlight_openapi_client.model.detector_type_enum import DetectorTypeEnum from groundlight_openapi_client.model.edge_model_info import EdgeModelInfo from groundlight_openapi_client.model.escalation_type_enum import EscalationTypeEnum @@ -50,6 +51,7 @@ from groundlight_openapi_client.model.multi_classification_result import MultiClassificationResult from groundlight_openapi_client.model.note import Note from groundlight_openapi_client.model.note_request import NoteRequest +from groundlight_openapi_client.model.null_enum import NullEnum from groundlight_openapi_client.model.paginated_detector_list import PaginatedDetectorList from groundlight_openapi_client.model.paginated_image_query_list import PaginatedImageQueryList from groundlight_openapi_client.model.paginated_ml_pipeline_list import PaginatedMLPipelineList diff --git a/generated/model.py b/generated/model.py index 3ed0feb4..c9161ebd 100644 --- a/generated/model.py +++ b/generated/model.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: public-api.yaml -# timestamp: 2026-04-07T00:20:58+00:00 +# timestamp: 2026-05-05T17:57:16+00:00 from __future__ import annotations @@ -75,6 +75,22 @@ class DetectorGroupRequest(BaseModel): name: constr(min_length=1, max_length=100) +class DetectorModeEnum(str, Enum): + """ + * `BINARY` - BINARY + * `COUNT` - COUNT + * `MULTI_CLASS` - MULTI_CLASS + * `TEXT` - TEXT + * `BOUNDING_BOX` - BOUNDING_BOX + """ + + BINARY = "BINARY" + COUNT = "COUNT" + MULTI_CLASS = "MULTI_CLASS" + TEXT = "TEXT" + BOUNDING_BOX = "BOUNDING_BOX" + + class DetectorTypeEnum(str, Enum): detector = "detector" @@ -100,12 +116,11 @@ class ImageQueryTypeEnum(str, Enum): class MLPipeline(BaseModel): """ A single ML pipeline attached to a detector. + Includes training status and metrics for the Pipeline Details UI. """ id: str - pipeline_config: Optional[str] = Field( - ..., description="configuration needed to instantiate a prediction pipeline." - ) + pipeline_config: str = Field(..., description="Get the resolved pipeline config, including defaults.") is_active_pipeline: bool = Field( ..., description=( @@ -120,6 +135,22 @@ class MLPipeline(BaseModel): is_enabled: bool = Field(..., description="If False, this pipeline will not be run for any use case.") created_at: datetime trained_at: Optional[datetime] = Field(...) + type: str = Field(..., description="Determine the pipeline type (active, shadow, unclear, oodd).") + friendly_name: Optional[str] = Field(..., description="Get the friendly name from the MLBinary.") + model_binary_id: Optional[str] = Field(...) + training_in_progress: Optional[Dict[str, Any]] = Field( + ..., description="Check if training is currently in progress for this pipeline." + ) + metrics: Dict[str, Any] = Field( + ..., description="Get the metrics for this pipeline from MLBinary metadata and evaluation results." + ) + eval_mlbinary_key: Optional[str] = Field(..., description="Get the MLBinary key that was evaluated.") + eval_mlbinary_revision_number: Optional[int] = Field( + ..., description="Get the revision number of the MLBinary that was evaluated." + ) + eval_mlbinary_friendly_name: Optional[str] = Field( + ..., description="Get the friendly name of the MLBinary that was evaluated." + ) class ModeEnum(str, Enum): @@ -142,6 +173,10 @@ class NoteRequest(BaseModel): image: Optional[bytes] = None +class NullEnum(Enum): + NoneType_None = None + + class PaginatedMLPipelineList(BaseModel): count: int = Field(..., examples=[123]) next: Optional[AnyUrl] = Field(None, examples=["http://api.example.org/accounts/?page=4"]) @@ -166,6 +201,23 @@ class PrimingGroup(BaseModel): id: str name: constr(max_length=100) + detector_mode: Optional[Union[DetectorModeEnum, BlankEnum, NullEnum]] = Field( + None, + description=( + "Detector mode this priming group is intended for (BINARY, MULTI_CLASS, etc.). Validated against the" + " pipeline_config of every active and shadow base MLBinary in clean(). Nullable only for legacy rows" + " pre-dating this field; new PrimingGroups must set it.\n\n* `BINARY` - BINARY\n* `COUNT` - COUNT\n*" + " `MULTI_CLASS` - MULTI_CLASS\n* `TEXT` - TEXT\n* `BOUNDING_BOX` - BOUNDING_BOX" + ), + ) + num_classes: Optional[int] = Field( + ..., + description=( + "Number of output classes this priming group is trained for. Set automatically from the source predictor" + " when created via create_user_priming_group (MULTI_CLASS only). Nullable for legacy rows and for modes" + " where class count isn't meaningful; a follow-up PR will tighten this after manual backfill." + ), + ) is_global: bool = Field( ..., description=( @@ -210,6 +262,14 @@ class PrimingGroupCreationInputRequest(BaseModel): source_ml_pipeline_id: constr(min_length=1, max_length=44) = Field( ..., description="ID of an MLPipeline owned by this account whose trained model will seed the priming group." ) + detector_mode: DetectorModeEnum = Field( + ..., + description=( + "Detector mode this priming group is intended for (BINARY, MULTI_CLASS, etc.). Must match the mode" + " supported by the source MLPipeline's pipeline_config.\n\n* `BINARY` - BINARY\n* `COUNT` - COUNT\n*" + " `MULTI_CLASS` - MULTI_CLASS\n* `TEXT` - TEXT\n* `BOUNDING_BOX` - BOUNDING_BOX" + ), + ) canonical_query: Optional[constr(max_length=300)] = Field( None, description="Optional canonical semantic query describing this priming group." ) diff --git a/generated/test/test_detector_mode_enum.py b/generated/test/test_detector_mode_enum.py new file mode 100644 index 00000000..b1323fd1 --- /dev/null +++ b/generated/test/test_detector_mode_enum.py @@ -0,0 +1,35 @@ +""" + Groundlight API + + Groundlight makes it simple to understand images. You can easily create computer vision detectors just by describing what you want to know using natural language. # noqa: E501 + + The version of the OpenAPI document: 0.18.2 + Contact: support@groundlight.ai + Generated by: https://openapi-generator.tech +""" + +import sys +import unittest + +import groundlight_openapi_client +from groundlight_openapi_client.model.detector_mode_enum import DetectorModeEnum + + +class TestDetectorModeEnum(unittest.TestCase): + """DetectorModeEnum unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testDetectorModeEnum(self): + """Test DetectorModeEnum""" + # FIXME: construct object with mandatory attributes with example values + # model = DetectorModeEnum() # noqa: E501 + pass + + +if __name__ == "__main__": + unittest.main() diff --git a/generated/test/test_null_enum.py b/generated/test/test_null_enum.py new file mode 100644 index 00000000..699f26ed --- /dev/null +++ b/generated/test/test_null_enum.py @@ -0,0 +1,35 @@ +""" + Groundlight API + + Groundlight makes it simple to understand images. You can easily create computer vision detectors just by describing what you want to know using natural language. # noqa: E501 + + The version of the OpenAPI document: 0.18.2 + Contact: support@groundlight.ai + Generated by: https://openapi-generator.tech +""" + +import sys +import unittest + +import groundlight_openapi_client +from groundlight_openapi_client.model.null_enum import NullEnum + + +class TestNullEnum(unittest.TestCase): + """NullEnum unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testNullEnum(self): + """Test NullEnum""" + # FIXME: construct object with mandatory attributes with example values + # model = NullEnum() # noqa: E501 + pass + + +if __name__ == "__main__": + unittest.main() diff --git a/package-lock.json b/package-lock.json index 7db9430a..51c40fa1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -184,6 +184,7 @@ "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.6.tgz", "integrity": "sha512-krKwLLcFmeuKDqngG2N/RuZHCs2ycsKcxWIDgcm7i1lf3sQ0iG03ci+DsP/r3FcT/eJDFsIHnKtNta2LIi7PzQ==", "license": "MIT", + "peer": true, "dependencies": { "file-type": "21.0.0", "iterare": "1.2.1", @@ -473,6 +474,7 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", "license": "MIT", + "peer": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", @@ -2569,7 +2571,8 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/rehype-katex": { "version": "7.0.1", @@ -2648,6 +2651,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.1.0" } diff --git a/spec/public-api.yaml b/spec/public-api.yaml index 69c63a44..ba95ac16 100644 --- a/spec/public-api.yaml +++ b/spec/public-api.yaml @@ -1277,6 +1277,20 @@ components: maxLength: 100 required: - name + DetectorModeEnum: + enum: + - BINARY + - COUNT + - MULTI_CLASS + - TEXT + - BOUNDING_BOX + type: string + description: |- + * `BINARY` - BINARY + * `COUNT` - COUNT + * `MULTI_CLASS` - MULTI_CLASS + * `TEXT` - TEXT + * `BOUNDING_BOX` - BOUNDING_BOX DetectorTypeEnum: enum: - detector @@ -1457,16 +1471,17 @@ components: - label MLPipeline: type: object - description: A single ML pipeline attached to a detector. + description: |- + A single ML pipeline attached to a detector. + Includes training status and metrics for the Pipeline Details UI. properties: id: type: string readOnly: true pipeline_config: type: string + description: Get the resolved pipeline config, including defaults. readOnly: true - nullable: true - description: configuration needed to instantiate a prediction pipeline. is_active_pipeline: type: boolean readOnly: true @@ -1498,16 +1513,63 @@ components: format: date-time readOnly: true nullable: true + type: + type: string + description: Determine the pipeline type (active, shadow, unclear, oodd). + readOnly: true + friendly_name: + type: string + nullable: true + description: Get the friendly name from the MLBinary. + readOnly: true + model_binary_id: + type: string + nullable: true + training_in_progress: + type: object + additionalProperties: {} + nullable: true + description: Check if training is currently in progress for this pipeline. + readOnly: true + metrics: + type: object + additionalProperties: {} + description: Get the metrics for this pipeline from MLBinary metadata and + evaluation results. + readOnly: true + eval_mlbinary_key: + type: string + nullable: true + description: Get the MLBinary key that was evaluated. + readOnly: true + eval_mlbinary_revision_number: + type: integer + nullable: true + description: Get the revision number of the MLBinary that was evaluated. + readOnly: true + eval_mlbinary_friendly_name: + type: string + nullable: true + description: Get the friendly name of the MLBinary that was evaluated. + readOnly: true required: - created_at + - eval_mlbinary_friendly_name + - eval_mlbinary_key + - eval_mlbinary_revision_number + - friendly_name - id - is_active_pipeline - is_edge_pipeline - is_enabled - is_oodd_pipeline - is_unclear_pipeline + - metrics + - model_binary_id - pipeline_config - trained_at + - training_in_progress + - type x-internal: true ModeEnum: type: string @@ -1547,6 +1609,9 @@ components: format: binary writeOnly: true nullable: true + NullEnum: + enum: + - null PaginatedDetectorList: type: object required: @@ -1735,6 +1800,29 @@ components: name: type: string maxLength: 100 + detector_mode: + nullable: true + description: |- + Detector mode this priming group is intended for (BINARY, MULTI_CLASS, etc.). Validated against the pipeline_config of every active and shadow base MLBinary in clean(). Nullable only for legacy rows pre-dating this field; new PrimingGroups must set it. + + * `BINARY` - BINARY + * `COUNT` - COUNT + * `MULTI_CLASS` - MULTI_CLASS + * `TEXT` - TEXT + * `BOUNDING_BOX` - BOUNDING_BOX + oneOf: + - $ref: '#/components/schemas/DetectorModeEnum' + - $ref: '#/components/schemas/BlankEnum' + - $ref: '#/components/schemas/NullEnum' + num_classes: + type: integer + readOnly: true + nullable: true + description: Number of output classes this priming group is trained for. + Set automatically from the source predictor when created via create_user_priming_group + (MULTI_CLASS only). Nullable for legacy rows and for modes where class + count isn't meaningful; a follow-up PR will tighten this after manual + backfill. is_global: type: boolean readOnly: true @@ -1774,6 +1862,7 @@ components: - id - is_global - name + - num_classes x-internal: true PrimingGroupCreationInputRequest: type: object @@ -1791,6 +1880,17 @@ components: description: ID of an MLPipeline owned by this account whose trained model will seed the priming group. maxLength: 44 + detector_mode: + allOf: + - $ref: '#/components/schemas/DetectorModeEnum' + description: |- + Detector mode this priming group is intended for (BINARY, MULTI_CLASS, etc.). Must match the mode supported by the source MLPipeline's pipeline_config. + + * `BINARY` - BINARY + * `COUNT` - COUNT + * `MULTI_CLASS` - MULTI_CLASS + * `TEXT` - TEXT + * `BOUNDING_BOX` - BOUNDING_BOX canonical_query: type: string nullable: true @@ -1803,6 +1903,7 @@ components: receive the default shadow pipelines. This guarantees the primed active model is never switched off. required: + - detector_mode - name - source_ml_pipeline_id x-internal: true diff --git a/src/groundlight/experimental_api.py b/src/groundlight/experimental_api.py index c4bf9c98..c32d038e 100644 --- a/src/groundlight/experimental_api.py +++ b/src/groundlight/experimental_api.py @@ -895,10 +895,11 @@ def list_priming_groups(self, page: int = 1, page_size: int = 10) -> PaginatedPr obj = self.priming_groups_api.list_priming_groups(page=page, page_size=page_size) return PaginatedPrimingGroupList.model_validate(obj.to_dict()) - def create_priming_group( + def create_priming_group( # noqa: PLR0913 # pylint: disable=too-many-arguments self, name: str, source_ml_pipeline_id: str, + detector_mode: ModeEnum, canonical_query: Optional[str] = None, disable_shadow_pipelines: bool = False, ) -> PrimingGroup: @@ -919,6 +920,7 @@ def create_priming_group( priming_group = gl.create_priming_group( name="door-detector-primer", source_ml_pipeline_id=active.id, + detector_mode=ModeEnum.BINARY, canonical_query="Is the door open?", disable_shadow_pipelines=True, ) @@ -927,6 +929,8 @@ def create_priming_group( :param name: A short, descriptive name for the priming group. :param source_ml_pipeline_id: The ID of an MLPipeline whose trained model will seed this group. The pipeline must belong to a detector in your account. + :param detector_mode: Detector mode this priming group is intended for (BINARY, MULTI_CLASS, etc.). + Must match the mode supported by the source MLPipeline's pipeline_config. :param canonical_query: An optional description of the visual question this group answers. :param disable_shadow_pipelines: If True, detectors created in this group will not receive default shadow pipelines, ensuring the primed model stays active. @@ -935,6 +939,7 @@ def create_priming_group( request = PrimingGroupCreationInputRequest( name=name, source_ml_pipeline_id=source_ml_pipeline_id, + detector_mode=ModeEnum(detector_mode).value, canonical_query=canonical_query, disable_shadow_pipelines=disable_shadow_pipelines, ) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 5fcd322d..aa5d0dca 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -25,6 +25,7 @@ MultiClassificationResult, PaginatedDetectorList, PaginatedImageQueryList, + TextRecognitionResult, ) from urllib3.exceptions import ConnectTimeoutError, MaxRetryError, ReadTimeoutError from urllib3.util.retry import Retry @@ -37,11 +38,15 @@ def is_valid_display_result(result: Any) -> bool: """Is the image query result valid to display to the user?.""" - if ( - not isinstance(result, BinaryClassificationResult) - and not isinstance(result, CountingResult) - and not isinstance(result, MultiClassificationResult) - and not isinstance(result, BoundingBoxResult) + if not isinstance( + result, + ( + BinaryClassificationResult, + CountingResult, + MultiClassificationResult, + BoundingBoxResult, + TextRecognitionResult, + ), ): return False diff --git a/test/integration/test_priming_groups.py b/test/integration/test_priming_groups.py index 38b2e382..20e95de7 100644 --- a/test/integration/test_priming_groups.py +++ b/test/integration/test_priming_groups.py @@ -9,11 +9,12 @@ """ import time +from typing import Callable import pytest from groundlight import ExperimentalApi from groundlight.internalapi import NotFoundError -from model import MLPipeline, PrimingGroup +from model import MLPipeline, ModeEnum, PrimingGroup # --------------------------------------------------------------------------- # list_detector_pipelines @@ -81,6 +82,34 @@ def _wait_for_trained_pipeline(gl_experimental: ExperimentalApi, detector, timeo raise TimeoutError(f"Detector {detector.id} did not produce a trained pipeline within {timeout}s") +def _wait_for_trained_multiclass_pipeline( + gl_experimental: ExperimentalApi, + detector, + class_names: list, + timeout: int = 120, +) -> MLPipeline: + """ + Multi-class analog of `_wait_for_trained_pipeline`: submit 3 labeled images per class + (cycling between the dog/cat fixtures so we have something to upload) and poll until + the active pipeline reports a trained_at timestamp. + """ + images = ["test/assets/dog.jpeg", "test/assets/cat.jpeg"] + for class_name in class_names: + for i in range(3): + iq = gl_experimental.submit_image_query(detector, images[i % len(images)], human_review="NEVER") + gl_experimental.add_label(iq, class_name) + + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + pipelines = gl_experimental.list_detector_pipelines(detector) + for p in pipelines.results: + if p.is_active_pipeline and p.trained_at is not None: + return p + time.sleep(5) + + raise TimeoutError(f"Detector {detector.id} did not produce a trained multiclass pipeline within {timeout}s") + + @pytest.mark.expensive def test_create_priming_group(gl_experimental: ExperimentalApi, detector): trained = _wait_for_trained_pipeline(gl_experimental, detector) @@ -88,6 +117,7 @@ def test_create_priming_group(gl_experimental: ExperimentalApi, detector): pg = gl_experimental.create_priming_group( name=f"test-primer-{detector.id}", source_ml_pipeline_id=trained.id, + detector_mode=ModeEnum.BINARY, canonical_query="Is there a dog?", ) @@ -108,6 +138,7 @@ def test_create_priming_group_disable_shadow_pipelines(gl_experimental: Experime pg = gl_experimental.create_priming_group( name=f"test-primer-noshadow-{detector.id}", source_ml_pipeline_id=trained.id, + detector_mode=ModeEnum.BINARY, disable_shadow_pipelines=True, ) @@ -123,6 +154,7 @@ def test_get_priming_group(gl_experimental: ExperimentalApi, detector): pg = gl_experimental.create_priming_group( name=f"test-primer-get-{detector.id}", source_ml_pipeline_id=trained.id, + detector_mode=ModeEnum.BINARY, ) fetched = gl_experimental.get_priming_group(pg.id) @@ -145,6 +177,7 @@ def test_delete_priming_group(gl_experimental: ExperimentalApi, detector): pg = gl_experimental.create_priming_group( name=f"test-primer-del-{detector.id}", source_ml_pipeline_id=trained.id, + detector_mode=ModeEnum.BINARY, ) gl_experimental.delete_priming_group(pg.id) @@ -160,9 +193,39 @@ def test_created_priming_group_appears_in_list(gl_experimental: ExperimentalApi, pg = gl_experimental.create_priming_group( name=f"test-primer-list-{detector.id}", source_ml_pipeline_id=trained.id, + detector_mode=ModeEnum.BINARY, ) groups = gl_experimental.list_priming_groups() assert any(g.id == pg.id for g in groups.results) gl_experimental.delete_priming_group(pg.id) + + +@pytest.mark.expensive +def test_create_priming_group_multiclass_populates_num_classes( + gl_experimental: ExperimentalApi, detector_name: Callable +): + """ + A priming group seeded from a MULTI_CLASS source records the source's class count on + `num_classes`. Confirms the auto-population path in `create_user_priming_group` works + end-to-end through the SDK. + """ + class_names = ["Golden Retriever", "Labrador Retriever", "Poodle"] + detector = gl_experimental.create_multiclass_detector( + name=detector_name("test-mc-source"), + query="What kind of dog is this?", + class_names=class_names, + confidence_threshold=0.0, + ) + trained = _wait_for_trained_multiclass_pipeline(gl_experimental, detector, class_names) + + pg = gl_experimental.create_priming_group( + name=f"test-mc-primer-{detector.id}", + source_ml_pipeline_id=trained.id, + detector_mode=ModeEnum.MULTI_CLASS, + ) + + assert pg.num_classes == len(class_names) + + gl_experimental.delete_priming_group(pg.id)