feat(mc): Add dynamic filter options endpoint for model catalog (#1727)
* feat: generate server and client files only Signed-off-by: Sidney Glinton <sglinton@redhat.com> * feat: add support for catalog dynamic catalog filtering Signed-off-by: Sidney Glinton <sglinton@redhat.com> * feat: catalog test fix Signed-off-by: Sidney Glinton <sglinton@redhat.com> * feat: add tests for filter options Signed-off-by: Sidney Glinton <sglinton@redhat.com> * fix: remove duplicate filterQuery parameter and regenerate OpenAPI files Signed-off-by: Sidney Glinton <sglinton@redhat.com> * fix: check range bug Signed-off-by: Sidney Glinton <sglinton@redhat.com> * feat: add filter options and fix openapi spec conflicts Signed-off-by: Sidney Glinton <sglinton@redhat.com> * feat: add filteroptionrange Signed-off-by: Sidney Glinton <sglinton@redhat.com> * fix: gitattributes file Signed-off-by: Sidney Glinton <sglinton@redhat.com> * fix: regenerate OpenAPI code and remove stale converter files Signed-off-by: Sidney Glinton <sglinton@redhat.com> * fix(catalog): regenerate files And fix badly generated openapi code. Signed-off-by: Paul Boyd <paul@pboyd.io> * Update api/openapi/src/catalog.yaml Co-authored-by: Paul Boyd <paul@pboyd.io> Signed-off-by: Sidney Glinton <sglinton@redhat.com> * Update api/openapi/src/catalog.yaml Co-authored-by: Paul Boyd <paul@pboyd.io> Signed-off-by: Sidney Glinton <sglinton@redhat.com> * Update api/openapi/src/catalog.yaml Co-authored-by: Paul Boyd <paul@pboyd.io> Signed-off-by: Sidney Glinton <sglinton@redhat.com> * fix: pr feedback changes Signed-off-by: Sidney Glinton <sglinton@redhat.com> * feat: regen files Signed-off-by: Sidney Glinton <sglinton@redhat.com> * fix: build and remove sourceLabel fields Signed-off-by: Sidney Glinton <sglinton@redhat.com> * fix: regen on openapi gen 7.13.0 Signed-off-by: Sidney Glinton <sglinton@redhat.com> * feat: regen openapi files after rebase Signed-off-by: Sidney Glinton <sglinton@redhat.com> * fix: build and tests and rebase Signed-off-by: Sidney Glinton <sglinton@redhat.com> * Update catalog/internal/server/openapi/api_model_catalog_service_service_test.go Co-authored-by: Paul Boyd <paul@pboyd.io> Signed-off-by: Sidney Glinton <sglinton@redhat.com> * fix: revert logic on older refactor Signed-off-by: Sidney Glinton <sglinton@redhat.com> * feat: regen files Signed-off-by: Sidney Glinton <sglinton@redhat.com> * feat: regen catalog Signed-off-by: Sidney Glinton <sglinton@redhat.com> --------- Signed-off-by: Sidney Glinton <sglinton@redhat.com> Signed-off-by: Paul Boyd <paul@pboyd.io> Co-authored-by: Paul Boyd <paul@pboyd.io>
This commit is contained in:
parent
ceaf413d44
commit
f61929fbae
|
|
@ -20,6 +20,9 @@ catalog/pkg/openapi/model_catalog_model_list.go linguist-generated=true
|
|||
catalog/pkg/openapi/model_catalog_source.go linguist-generated=true
|
||||
catalog/pkg/openapi/model_catalog_source_list.go linguist-generated=true
|
||||
catalog/pkg/openapi/model_error.go linguist-generated=true
|
||||
catalog/pkg/openapi/model_filter_option.go linguist-generated=true
|
||||
catalog/pkg/openapi/model_filter_option_range.go linguist-generated=true
|
||||
catalog/pkg/openapi/model_filter_options_list.go linguist-generated=true
|
||||
catalog/pkg/openapi/model_metadata_bool_value.go linguist-generated=true
|
||||
catalog/pkg/openapi/model_metadata_double_value.go linguist-generated=true
|
||||
catalog/pkg/openapi/model_metadata_int_value.go linguist-generated=true
|
||||
|
|
|
|||
|
|
@ -67,6 +67,22 @@ paths:
|
|||
"500":
|
||||
$ref: "#/components/responses/InternalServerError"
|
||||
operationId: findModels
|
||||
/api/model_catalog/v1alpha1/models/filter_options:
|
||||
description: Lists options for `filterQuery` when listing models.
|
||||
get:
|
||||
summary: Lists fields and available options that can be used in `filterQuery` on the list models endpoint.
|
||||
tags:
|
||||
- ModelCatalogService
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/FilterOptionsResponse"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalServerError"
|
||||
operationId: findModelsFilterOptions
|
||||
/api/model_catalog/v1alpha1/sources:
|
||||
summary: Path used to get the list of catalog sources.
|
||||
description: >-
|
||||
|
|
@ -434,6 +450,40 @@ components:
|
|||
message:
|
||||
description: Error message
|
||||
type: string
|
||||
FilterOption:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
description: The data type of the filter option
|
||||
enum:
|
||||
- string
|
||||
- number
|
||||
values:
|
||||
type: array
|
||||
description: Known values of the property for string types with a small number of possible options.
|
||||
items: {}
|
||||
range:
|
||||
$ref: "#/components/schemas/FilterOptionRange"
|
||||
FilterOptionRange:
|
||||
type: object
|
||||
description: Min and max values for number types.
|
||||
properties:
|
||||
min:
|
||||
type: number
|
||||
max:
|
||||
type: number
|
||||
FilterOptionsList:
|
||||
description: List of FilterOptions
|
||||
type: object
|
||||
properties:
|
||||
filters:
|
||||
type: object
|
||||
description: A single filter option.
|
||||
additionalProperties:
|
||||
$ref: "#/components/schemas/FilterOption"
|
||||
MetadataBoolValue:
|
||||
description: A bool property value.
|
||||
type: object
|
||||
|
|
@ -598,6 +648,12 @@ components:
|
|||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
description: Conflict with current state of target resource
|
||||
FilterOptionsResponse:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/FilterOptionsList"
|
||||
description: A response containing options for a `filterQuery` parameter.
|
||||
InternalServerError:
|
||||
content:
|
||||
application/json:
|
||||
|
|
|
|||
|
|
@ -67,6 +67,22 @@ paths:
|
|||
"500":
|
||||
$ref: "#/components/responses/InternalServerError"
|
||||
operationId: findModels
|
||||
/api/model_catalog/v1alpha1/models/filter_options:
|
||||
description: Lists options for `filterQuery` when listing models.
|
||||
get:
|
||||
summary: Lists fields and available options that can be used in `filterQuery` on the list models endpoint.
|
||||
tags:
|
||||
- ModelCatalogService
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/FilterOptionsResponse"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalServerError"
|
||||
operationId: findModelsFilterOptions
|
||||
/api/model_catalog/v1alpha1/sources:
|
||||
summary: Path used to get the list of catalog sources.
|
||||
description: >-
|
||||
|
|
@ -295,6 +311,40 @@ components:
|
|||
items:
|
||||
$ref: "#/components/schemas/CatalogSource"
|
||||
- $ref: "#/components/schemas/BaseResourceList"
|
||||
FilterOption:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
description: The data type of the filter option
|
||||
enum:
|
||||
- string
|
||||
- number
|
||||
values:
|
||||
type: array
|
||||
description: Known values of the property for string types with a small number of possible options.
|
||||
items: {}
|
||||
range:
|
||||
$ref: "#/components/schemas/FilterOptionRange"
|
||||
FilterOptionRange:
|
||||
type: object
|
||||
description: Min and max values for number types.
|
||||
properties:
|
||||
min:
|
||||
type: number
|
||||
max:
|
||||
type: number
|
||||
FilterOptionsList:
|
||||
description: List of FilterOptions
|
||||
type: object
|
||||
properties:
|
||||
filters:
|
||||
type: object
|
||||
description: A single filter option.
|
||||
additionalProperties:
|
||||
$ref: "#/components/schemas/FilterOption"
|
||||
OrderByField:
|
||||
description: Supported fields for ordering result entities.
|
||||
enum:
|
||||
|
|
@ -335,6 +385,12 @@ components:
|
|||
schema:
|
||||
$ref: "#/components/schemas/CatalogSource"
|
||||
description: A response containing a `CatalogSource` entity.
|
||||
FilterOptionsResponse:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/FilterOptionsList"
|
||||
description: A response containing options for a `filterQuery` parameter.
|
||||
|
||||
parameters:
|
||||
orderBy:
|
||||
|
|
|
|||
|
|
@ -41,4 +41,8 @@ type APIProvider interface {
|
|||
// model is found with that name, it returns nil. If the model is
|
||||
// found, but has no artifacts, an empty list is returned.
|
||||
GetArtifacts(ctx context.Context, modelName string, sourceID string, params ListArtifactsParams) (model.CatalogArtifactList, error)
|
||||
|
||||
// GetFilterOptions returns all available filter options for models.
|
||||
// This includes field names, data types, and available values or ranges.
|
||||
GetFilterOptions(ctx context.Context) (*model.FilterOptionsList, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -416,6 +416,10 @@ func (m *MockCatalogModelRepository) Save(model dbmodels.CatalogModel) (dbmodels
|
|||
return savedModel, nil
|
||||
}
|
||||
|
||||
func (m *MockCatalogModelRepository) GetFilterableProperties(maxLength int) (map[string][]string, error) {
|
||||
return make(map[string][]string), nil
|
||||
}
|
||||
|
||||
// MockCatalogModelArtifactRepository mocks the CatalogModelArtifactRepository interface.
|
||||
type MockCatalogModelArtifactRepository struct {
|
||||
SavedArtifacts []dbmodels.CatalogModelArtifact
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
dbmodels "github.com/kubeflow/model-registry/catalog/internal/db/models"
|
||||
|
|
@ -184,6 +185,68 @@ func (d *dbCatalogImpl) GetArtifacts(ctx context.Context, modelName string, sour
|
|||
return *artifactList, nil
|
||||
}
|
||||
|
||||
func (d *dbCatalogImpl) GetFilterOptions(ctx context.Context) (*apimodels.FilterOptionsList, error) {
|
||||
// Max length threshold for filter values (excludes verbose fields like readme, description)
|
||||
const maxFilterValueLength = 100
|
||||
|
||||
// Query database for filterable properties
|
||||
filterableProps, err := d.catalogModelRepository.GetFilterableProperties(maxFilterValueLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Build FilterOptionsList
|
||||
options := make(map[string]apimodels.FilterOption, maxFilterValueLength)
|
||||
|
||||
// Process each property and its values
|
||||
for fieldName, values := range filterableProps {
|
||||
// Skip internal/technical fields that shouldn't be exposed as filters
|
||||
if fieldName == "source_id" || fieldName == "logo" || fieldName == "license_link" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Deduplicate values
|
||||
uniqueValues := make(map[string]bool)
|
||||
|
||||
// Parse JSON arrays for fields like language and tasks
|
||||
for _, value := range values {
|
||||
var arrayValues []string
|
||||
if err := json.Unmarshal([]byte(value), &arrayValues); err == nil {
|
||||
// Successfully parsed as array, add individual values
|
||||
for _, v := range arrayValues {
|
||||
uniqueValues[v] = true
|
||||
}
|
||||
} else {
|
||||
// Not a JSON array
|
||||
uniqueValues[value] = true
|
||||
}
|
||||
}
|
||||
|
||||
if len(uniqueValues) > 0 {
|
||||
sortedValues := make([]string, 0, len(uniqueValues))
|
||||
for v := range uniqueValues {
|
||||
sortedValues = append(sortedValues, v)
|
||||
}
|
||||
sort.Strings(sortedValues)
|
||||
|
||||
// Convert to []interface{} (supports future non-string filter types)
|
||||
expandedValues := make([]interface{}, len(sortedValues))
|
||||
for i, v := range sortedValues {
|
||||
expandedValues[i] = v
|
||||
}
|
||||
|
||||
options[fieldName] = apimodels.FilterOption{
|
||||
Type: "string",
|
||||
Values: expandedValues,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &apimodels.FilterOptionsList{
|
||||
Filters: &options,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapDBModelToAPIModel(m dbmodels.CatalogModel) apimodels.CatalogModel {
|
||||
res := apimodels.CatalogModel{}
|
||||
|
||||
|
|
|
|||
|
|
@ -801,6 +801,168 @@ func TestDBCatalog(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "invalid model name")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("TestGetFilterOptions", func(t *testing.T) {
|
||||
// Create models with various properties for filter options testing
|
||||
model1 := &models.CatalogModelImpl{
|
||||
TypeID: apiutils.Of(int32(catalogModelTypeID)),
|
||||
Attributes: &models.CatalogModelAttributes{
|
||||
Name: apiutils.Of("filter-options-model-1"),
|
||||
ExternalID: apiutils.Of("filter-opt-1"),
|
||||
},
|
||||
Properties: &[]mr_models.Properties{
|
||||
{Name: "source_id", StringValue: apiutils.Of("filter-test-source")},
|
||||
{Name: "license", StringValue: apiutils.Of("MIT")},
|
||||
{Name: "provider", StringValue: apiutils.Of("HuggingFace")},
|
||||
{Name: "maturity", StringValue: apiutils.Of("stable")},
|
||||
{Name: "library_name", StringValue: apiutils.Of("transformers")},
|
||||
{Name: "language", StringValue: apiutils.Of(`["python", "rust"]`)},
|
||||
{Name: "tasks", StringValue: apiutils.Of(`["text-classification", "token-classification"]`)},
|
||||
},
|
||||
}
|
||||
|
||||
model2 := &models.CatalogModelImpl{
|
||||
TypeID: apiutils.Of(int32(catalogModelTypeID)),
|
||||
Attributes: &models.CatalogModelAttributes{
|
||||
Name: apiutils.Of("filter-options-model-2"),
|
||||
ExternalID: apiutils.Of("filter-opt-2"),
|
||||
},
|
||||
Properties: &[]mr_models.Properties{
|
||||
{Name: "source_id", StringValue: apiutils.Of("filter-test-source")},
|
||||
{Name: "license", StringValue: apiutils.Of("Apache-2.0")},
|
||||
{Name: "provider", StringValue: apiutils.Of("OpenAI")},
|
||||
{Name: "maturity", StringValue: apiutils.Of("experimental")},
|
||||
{Name: "library_name", StringValue: apiutils.Of("openai")},
|
||||
{Name: "language", StringValue: apiutils.Of(`["python", "javascript"]`)},
|
||||
{Name: "tasks", StringValue: apiutils.Of(`["text-generation", "conversational"]`)},
|
||||
{Name: "readme", StringValue: apiutils.Of("This is a very long readme that exceeds 100 characters and should be excluded from filter options because it's too verbose for filtering purposes.")},
|
||||
},
|
||||
}
|
||||
|
||||
model3 := &models.CatalogModelImpl{
|
||||
TypeID: apiutils.Of(int32(catalogModelTypeID)),
|
||||
Attributes: &models.CatalogModelAttributes{
|
||||
Name: apiutils.Of("filter-options-model-3"),
|
||||
ExternalID: apiutils.Of("filter-opt-3"),
|
||||
},
|
||||
Properties: &[]mr_models.Properties{
|
||||
{Name: "source_id", StringValue: apiutils.Of("filter-test-source")},
|
||||
{Name: "license", StringValue: apiutils.Of("MIT")},
|
||||
{Name: "provider", StringValue: apiutils.Of("PyTorch")},
|
||||
{Name: "maturity", StringValue: apiutils.Of("stable")},
|
||||
{Name: "language", StringValue: apiutils.Of(`["python"]`)},
|
||||
{Name: "tasks", StringValue: apiutils.Of(`["image-classification"]`)},
|
||||
{Name: "logo", StringValue: apiutils.Of("https://example.com/logo.png")},
|
||||
{Name: "license_link", StringValue: apiutils.Of("https://example.com/license")},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := catalogModelRepo.Save(model1)
|
||||
require.NoError(t, err)
|
||||
_, err = catalogModelRepo.Save(model2)
|
||||
require.NoError(t, err)
|
||||
_, err = catalogModelRepo.Save(model3)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test GetFilterOptions
|
||||
filterOptions, err := dbCatalog.GetFilterOptions(ctx)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, filterOptions)
|
||||
require.NotNil(t, filterOptions.Filters)
|
||||
|
||||
filters := *filterOptions.Filters
|
||||
|
||||
// Should include short properties
|
||||
assert.Contains(t, filters, "license")
|
||||
assert.Contains(t, filters, "provider")
|
||||
assert.Contains(t, filters, "maturity")
|
||||
assert.Contains(t, filters, "library_name")
|
||||
assert.Contains(t, filters, "language")
|
||||
assert.Contains(t, filters, "tasks")
|
||||
|
||||
// Should exclude internal/verbose fields
|
||||
assert.NotContains(t, filters, "source_id", "source_id should be excluded")
|
||||
assert.NotContains(t, filters, "logo", "logo should be excluded")
|
||||
assert.NotContains(t, filters, "license_link", "license_link should be excluded")
|
||||
assert.NotContains(t, filters, "readme", "readme should be excluded (too long)")
|
||||
|
||||
licenseFilter := filters["license"]
|
||||
assert.Equal(t, "string", licenseFilter.Type)
|
||||
assert.NotNil(t, licenseFilter.Values)
|
||||
assert.GreaterOrEqual(t, len(licenseFilter.Values), 2, "Should have at least MIT and Apache-2.0")
|
||||
|
||||
// Convert to string slice for easier checking
|
||||
licenseValues := make([]string, 0)
|
||||
for _, v := range licenseFilter.Values {
|
||||
if strVal, ok := v.(string); ok {
|
||||
licenseValues = append(licenseValues, strVal)
|
||||
}
|
||||
}
|
||||
assert.Contains(t, licenseValues, "MIT")
|
||||
assert.Contains(t, licenseValues, "Apache-2.0")
|
||||
|
||||
// Verify provider filter options
|
||||
providerFilter := filters["provider"]
|
||||
assert.Equal(t, "string", providerFilter.Type)
|
||||
providerValues := make([]string, 0)
|
||||
for _, v := range providerFilter.Values {
|
||||
if strVal, ok := v.(string); ok {
|
||||
providerValues = append(providerValues, strVal)
|
||||
}
|
||||
}
|
||||
assert.Contains(t, providerValues, "HuggingFace")
|
||||
assert.Contains(t, providerValues, "OpenAI")
|
||||
assert.Contains(t, providerValues, "PyTorch")
|
||||
|
||||
// Verify JSON array fields are properly parsed and expanded
|
||||
languageFilter := filters["language"]
|
||||
assert.Equal(t, "string", languageFilter.Type)
|
||||
languageValues := make([]string, 0)
|
||||
for _, v := range languageFilter.Values {
|
||||
if strVal, ok := v.(string); ok {
|
||||
languageValues = append(languageValues, strVal)
|
||||
}
|
||||
}
|
||||
// Should contain individual values from JSON arrays
|
||||
assert.Contains(t, languageValues, "python")
|
||||
assert.Contains(t, languageValues, "rust")
|
||||
assert.Contains(t, languageValues, "javascript")
|
||||
|
||||
// Verify tasks are properly expanded
|
||||
tasksFilter := filters["tasks"]
|
||||
assert.Equal(t, "string", tasksFilter.Type)
|
||||
tasksValues := make([]string, 0)
|
||||
for _, v := range tasksFilter.Values {
|
||||
if strVal, ok := v.(string); ok {
|
||||
tasksValues = append(tasksValues, strVal)
|
||||
}
|
||||
}
|
||||
assert.Contains(t, tasksValues, "text-classification")
|
||||
assert.Contains(t, tasksValues, "token-classification")
|
||||
assert.Contains(t, tasksValues, "text-generation")
|
||||
assert.Contains(t, tasksValues, "conversational")
|
||||
assert.Contains(t, tasksValues, "image-classification")
|
||||
|
||||
// Verify no duplicates
|
||||
pythonCount := 0
|
||||
for _, v := range languageValues {
|
||||
if v == "python" {
|
||||
pythonCount++
|
||||
}
|
||||
}
|
||||
assert.Equal(t, 1, pythonCount, "python should appear only once (deduplicated)")
|
||||
|
||||
// Verify maturity options
|
||||
maturityFilter := filters["maturity"]
|
||||
maturityValues := make([]string, 0)
|
||||
for _, v := range maturityFilter.Values {
|
||||
if strVal, ok := v.(string); ok {
|
||||
maturityValues = append(maturityValues, strVal)
|
||||
}
|
||||
}
|
||||
assert.Contains(t, maturityValues, "stable")
|
||||
assert.Contains(t, maturityValues, "experimental")
|
||||
})
|
||||
}
|
||||
|
||||
// Helper functions to get type IDs from database
|
||||
|
|
|
|||
|
|
@ -49,6 +49,15 @@ func (h *hfCatalogImpl) GetArtifacts(ctx context.Context, modelName string, sour
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (h *hfCatalogImpl) GetFilterOptions(ctx context.Context) (*openapi.FilterOptionsList, error) {
|
||||
// TODO: Implement HuggingFace filter options retrieval
|
||||
// For now, return empty options to satisfy interface
|
||||
emptyFilters := make(map[string]openapi.FilterOption)
|
||||
return &openapi.FilterOptionsList{
|
||||
Filters: &emptyFilters,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// validateCredentials checks if the HuggingFace API credentials are valid
|
||||
func (h *hfCatalogImpl) validateCredentials(ctx context.Context) error {
|
||||
glog.Infof("Validating HuggingFace API credentials")
|
||||
|
|
|
|||
|
|
@ -37,4 +37,7 @@ type CatalogModelRepository interface {
|
|||
GetByName(name string) (CatalogModel, error)
|
||||
List(listOptions CatalogModelListOptions) (*models.ListWrapper[CatalogModel], error)
|
||||
Save(model CatalogModel) (CatalogModel, error)
|
||||
// GetFilterableProperties returns a map of property names to their unique values
|
||||
// Only includes string properties where all values are shorter than maxLength
|
||||
GetFilterableProperties(maxLength int) (map[string][]string, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -231,3 +231,56 @@ func mapDataLayerToCatalogModel(modelCtx schema.Context, propertiesCtx []schema.
|
|||
|
||||
return catalogModel
|
||||
}
|
||||
|
||||
// GetFilterableProperties returns property names and their unique values
|
||||
// Only includes properties where ALL values are shorter than maxLength
|
||||
func (r *CatalogModelRepositoryImpl) GetFilterableProperties(maxLength int) (map[string][]string, error) {
|
||||
config := r.GetConfig()
|
||||
|
||||
// Get table names using GORM utilities for database compatibility
|
||||
contextTable := utils.GetTableName(config.DB, &schema.Context{})
|
||||
propertyTable := utils.GetTableName(config.DB, &schema.ContextProperty{})
|
||||
|
||||
// Simplified query: get distinct property name/value pairs
|
||||
query := fmt.Sprintf(`
|
||||
SELECT DISTINCT cp.name, cp.string_value
|
||||
FROM %s cp
|
||||
WHERE cp.context_id IN (
|
||||
SELECT id FROM %s WHERE type_id = ?
|
||||
)
|
||||
AND cp.name IN (
|
||||
SELECT name FROM (
|
||||
SELECT name, MAX(CHAR_LENGTH(string_value)) as max_len
|
||||
FROM %s
|
||||
WHERE context_id IN (
|
||||
SELECT id FROM %s WHERE type_id = ?
|
||||
)
|
||||
AND string_value IS NOT NULL
|
||||
AND string_value != ''
|
||||
GROUP BY name
|
||||
) AS field_lengths
|
||||
WHERE max_len <= ?
|
||||
)
|
||||
AND cp.string_value IS NOT NULL
|
||||
AND cp.string_value != ''
|
||||
ORDER BY cp.name, cp.string_value
|
||||
`, propertyTable, contextTable, propertyTable, contextTable)
|
||||
|
||||
type propertyRow struct {
|
||||
Name string
|
||||
StringValue string
|
||||
}
|
||||
|
||||
var rows []propertyRow
|
||||
if err := config.DB.Raw(query, config.TypeID, config.TypeID, maxLength).Scan(&rows).Error; err != nil {
|
||||
return nil, fmt.Errorf("error querying filterable properties: %w", err)
|
||||
}
|
||||
|
||||
// Aggregate values by property name in Go
|
||||
result := make(map[string][]string)
|
||||
for _, row := range rows {
|
||||
result[row.Name] = append(result[row.Name], row.StringValue)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -337,6 +337,85 @@ func TestCatalogModelRepository(t *testing.T) {
|
|||
require.NotNil(t, retrieved.GetCustomProperties())
|
||||
assert.Len(t, *retrieved.GetCustomProperties(), 2)
|
||||
})
|
||||
|
||||
t.Run("TestGetFilterableProperties", func(t *testing.T) {
|
||||
// Create models with various property lengths
|
||||
shortValueModel := &models.CatalogModelImpl{
|
||||
Attributes: &models.CatalogModelAttributes{
|
||||
Name: apiutils.Of("short-value-model"),
|
||||
ExternalID: apiutils.Of("short-ext"),
|
||||
},
|
||||
Properties: &[]dbmodels.Properties{
|
||||
{Name: "license", StringValue: apiutils.Of("MIT")},
|
||||
{Name: "provider", StringValue: apiutils.Of("HuggingFace")},
|
||||
{Name: "maturity", StringValue: apiutils.Of("stable")},
|
||||
},
|
||||
}
|
||||
|
||||
longValueModel := &models.CatalogModelImpl{
|
||||
Attributes: &models.CatalogModelAttributes{
|
||||
Name: apiutils.Of("long-value-model"),
|
||||
ExternalID: apiutils.Of("long-ext"),
|
||||
},
|
||||
Properties: &[]dbmodels.Properties{
|
||||
{Name: "license", StringValue: apiutils.Of("Apache-2.0")},
|
||||
{Name: "readme", StringValue: apiutils.Of("This is a very long readme that should be excluded from filterable properties because it exceeds the maximum length threshold of 100 characters. It contains detailed information about the model.")},
|
||||
{Name: "description", StringValue: apiutils.Of("This is also a very long description that should be excluded from filterable properties because it exceeds 100 chars")},
|
||||
},
|
||||
}
|
||||
|
||||
jsonArrayModel := &models.CatalogModelImpl{
|
||||
Attributes: &models.CatalogModelAttributes{
|
||||
Name: apiutils.Of("json-array-model"),
|
||||
ExternalID: apiutils.Of("json-ext"),
|
||||
},
|
||||
Properties: &[]dbmodels.Properties{
|
||||
{Name: "language", StringValue: apiutils.Of(`["python", "go"]`)},
|
||||
{Name: "tasks", StringValue: apiutils.Of(`["text-classification", "question-answering"]`)},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := repo.Save(shortValueModel)
|
||||
require.NoError(t, err)
|
||||
_, err = repo.Save(longValueModel)
|
||||
require.NoError(t, err)
|
||||
_, err = repo.Save(jsonArrayModel)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test with max length of 100
|
||||
result, err := repo.GetFilterableProperties(100)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result)
|
||||
|
||||
// Should include short properties
|
||||
assert.Contains(t, result, "license")
|
||||
assert.Contains(t, result, "provider")
|
||||
assert.Contains(t, result, "maturity")
|
||||
assert.Contains(t, result, "language")
|
||||
assert.Contains(t, result, "tasks")
|
||||
|
||||
// Should exclude long properties
|
||||
assert.NotContains(t, result, "readme")
|
||||
assert.NotContains(t, result, "description")
|
||||
|
||||
// Verify license has both values
|
||||
licenseValues := result["license"]
|
||||
assert.GreaterOrEqual(t, len(licenseValues), 2)
|
||||
assert.Contains(t, licenseValues, "MIT")
|
||||
assert.Contains(t, licenseValues, "Apache-2.0")
|
||||
|
||||
// Test with smaller max length
|
||||
result, err = repo.GetFilterableProperties(10)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result)
|
||||
|
||||
// Should include only very short properties
|
||||
assert.Contains(t, result, "license")
|
||||
// Should exclude longer properties
|
||||
assert.NotContains(t, result, "provider") // "HuggingFace" is > 10 chars
|
||||
assert.NotContains(t, result, "language")
|
||||
assert.NotContains(t, result, "tasks")
|
||||
})
|
||||
}
|
||||
|
||||
// Helper function to get or create CatalogModel type ID
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ model_catalog_model_list.go
|
|||
model_catalog_source.go
|
||||
model_catalog_source_list.go
|
||||
model_error.go
|
||||
model_filter_option.go
|
||||
model_filter_option_range.go
|
||||
model_filter_options_list.go
|
||||
model_metadata_bool_value.go
|
||||
model_metadata_double_value.go
|
||||
model_metadata_int_value.go
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
// pass the data to a ModelCatalogServiceAPIServicer to perform the required actions, then write the service results to the http response.
|
||||
type ModelCatalogServiceAPIRouter interface {
|
||||
FindModels(http.ResponseWriter, *http.Request)
|
||||
FindModelsFilterOptions(http.ResponseWriter, *http.Request)
|
||||
FindSources(http.ResponseWriter, *http.Request)
|
||||
GetModel(http.ResponseWriter, *http.Request)
|
||||
GetAllModelArtifacts(http.ResponseWriter, *http.Request)
|
||||
|
|
@ -33,6 +34,7 @@ type ModelCatalogServiceAPIRouter interface {
|
|||
// and updated with the logic required for the API.
|
||||
type ModelCatalogServiceAPIServicer interface {
|
||||
FindModels(context.Context, []string, string, []string, string, string, model.OrderByField, model.SortOrder, string) (ImplResponse, error)
|
||||
FindModelsFilterOptions(context.Context) (ImplResponse, error)
|
||||
FindSources(context.Context, string, string, model.OrderByField, model.SortOrder, string) (ImplResponse, error)
|
||||
GetModel(context.Context, string, string) (ImplResponse, error)
|
||||
GetAllModelArtifacts(context.Context, string, string, string, string, model.OrderByField, model.SortOrder, string) (ImplResponse, error)
|
||||
|
|
|
|||
|
|
@ -57,6 +57,11 @@ func (c *ModelCatalogServiceAPIController) Routes() Routes {
|
|||
"/api/model_catalog/v1alpha1/models",
|
||||
c.FindModels,
|
||||
},
|
||||
"FindModelsFilterOptions": Route{
|
||||
strings.ToUpper("Get"),
|
||||
"/api/model_catalog/v1alpha1/models/filter_options",
|
||||
c.FindModelsFilterOptions,
|
||||
},
|
||||
"FindSources": Route{
|
||||
strings.ToUpper("Get"),
|
||||
"/api/model_catalog/v1alpha1/sources",
|
||||
|
|
@ -96,6 +101,18 @@ func (c *ModelCatalogServiceAPIController) FindModels(w http.ResponseWriter, r *
|
|||
EncodeJSONResponse(result.Body, &result.Code, w)
|
||||
}
|
||||
|
||||
// FindModelsFilterOptions - Lists fields and available options that can be used in `filterQuery` on the list models endpoint.
|
||||
func (c *ModelCatalogServiceAPIController) FindModelsFilterOptions(w http.ResponseWriter, r *http.Request) {
|
||||
result, err := c.service.FindModelsFilterOptions(r.Context())
|
||||
// If an error occurred, encode the error with the status code
|
||||
if err != nil {
|
||||
c.errorHandler(w, r, err, &result)
|
||||
return
|
||||
}
|
||||
// If no error, encode the body and the result code
|
||||
EncodeJSONResponse(result.Body, &result.Code, w)
|
||||
}
|
||||
|
||||
// FindSources - List All CatalogSources
|
||||
func (c *ModelCatalogServiceAPIController) FindSources(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.URL.Query()
|
||||
|
|
|
|||
|
|
@ -42,11 +42,11 @@ func (m *ModelCatalogServiceAPIService) GetAllModelArtifacts(ctx context.Context
|
|||
}
|
||||
|
||||
artifacts, err := m.provider.GetArtifacts(ctx, modelName, sourceID, catalog.ListArtifactsParams{
|
||||
ArtifactType: &artifactType,
|
||||
PageSize: pageSizeInt,
|
||||
OrderBy: orderBy,
|
||||
SortOrder: sortOrder,
|
||||
NextPageToken: &nextPageToken,
|
||||
ArtifactType: &artifactType,
|
||||
})
|
||||
if err != nil {
|
||||
statusCode := api.ErrToStatus(err)
|
||||
|
|
@ -64,7 +64,7 @@ func (m *ModelCatalogServiceAPIService) GetAllModelArtifacts(ctx context.Context
|
|||
return Response(http.StatusOK, artifacts), nil
|
||||
}
|
||||
|
||||
func (m *ModelCatalogServiceAPIService) FindModels(ctx context.Context, sourceIDs []string, q string, sourceLabels []string, filterQuery, pageSize string, orderBy model.OrderByField, sortOrder model.SortOrder, nextPageToken string) (ImplResponse, error) {
|
||||
func (m *ModelCatalogServiceAPIService) FindModels(ctx context.Context, sourceIDs []string, q string, sourceLabels []string, filterQuery string, pageSize string, orderBy model.OrderByField, sortOrder model.SortOrder, nextPageToken string) (ImplResponse, error) {
|
||||
var err error
|
||||
pageSizeInt := int32(10)
|
||||
|
||||
|
|
@ -107,6 +107,15 @@ func (m *ModelCatalogServiceAPIService) FindModels(ctx context.Context, sourceID
|
|||
return Response(http.StatusOK, models), nil
|
||||
}
|
||||
|
||||
func (m *ModelCatalogServiceAPIService) FindModelsFilterOptions(ctx context.Context) (ImplResponse, error) {
|
||||
filterOptions, err := m.provider.GetFilterOptions(ctx)
|
||||
if err != nil {
|
||||
return ErrorResponse(http.StatusInternalServerError, err), err
|
||||
}
|
||||
|
||||
return Response(http.StatusOK, filterOptions), nil
|
||||
}
|
||||
|
||||
func (m *ModelCatalogServiceAPIService) GetModel(ctx context.Context, sourceID, modelName string) (ImplResponse, error) {
|
||||
if newName, err := url.PathUnescape(modelName); err == nil {
|
||||
modelName = newName
|
||||
|
|
|
|||
|
|
@ -776,6 +776,11 @@ func (m *mockModelProvider) GetArtifacts(ctx context.Context, name string, sourc
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (m *mockModelProvider) GetFilterOptions(ctx context.Context) (*model.FilterOptionsList, error) {
|
||||
emptyFilters := make(map[string]model.FilterOption)
|
||||
return &model.FilterOptionsList{Filters: &emptyFilters}, nil
|
||||
}
|
||||
|
||||
func TestGetModel(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
|
|
@ -1002,3 +1007,44 @@ func TestGetAllModelArtifacts(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindModelsFilterOptions(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
provider catalog.APIProvider
|
||||
expectedStatus int
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
name: "Successfully retrieve filter options",
|
||||
provider: &mockModelProvider{
|
||||
models: map[string]*model.CatalogModel{},
|
||||
},
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
sources := catalog.NewSourceCollection()
|
||||
service := NewModelCatalogServiceAPIService(tc.provider, sources)
|
||||
|
||||
resp, err := service.FindModelsFilterOptions(context.Background())
|
||||
|
||||
assert.Equal(t, tc.expectedStatus, resp.Code)
|
||||
|
||||
if tc.expectedError {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NotNil(t, resp.Body)
|
||||
|
||||
// Type assertion to access the FilterOptionsList
|
||||
filterOptions, ok := resp.Body.(*model.FilterOptionsList)
|
||||
require.True(t, ok, "Response body should be a FilterOptionsList")
|
||||
|
||||
require.NotNil(t, filterOptions.Filters)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -257,6 +257,26 @@ func AssertErrorRequired(obj model.Error) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// AssertFilterOptionRangeConstraints checks if the values respects the defined constraints
|
||||
func AssertFilterOptionRangeConstraints(obj model.FilterOptionRange) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertFilterOptionRangeRequired checks if the required fields are not zero-ed
|
||||
func AssertFilterOptionRangeRequired(obj model.FilterOptionRange) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertFilterOptionsListConstraints checks if the values respects the defined constraints
|
||||
func AssertFilterOptionsListConstraints(obj model.FilterOptionsList) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertFilterOptionsListRequired checks if the required fields are not zero-ed
|
||||
func AssertFilterOptionsListRequired(obj model.FilterOptionsList) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertMetadataBoolValueConstraints checks if the values respects the defined constraints
|
||||
func AssertMetadataBoolValueConstraints(obj model.MetadataBoolValue) error {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -10,3 +10,22 @@ func AssertCatalogArtifactRequired(obj model.CatalogArtifact) error {
|
|||
// checks the fields from CatalogModelArtifact, which doesn't compile.
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertFilterOptionRequired checks if the required fields are not zero-ed
|
||||
func AssertFilterOptionRequired(obj model.FilterOption) error {
|
||||
elements := map[string]interface{}{
|
||||
"type": obj.Type,
|
||||
}
|
||||
for name, el := range elements {
|
||||
if isZero := IsZeroValue(el); isZero {
|
||||
return &RequiredError{Field: name}
|
||||
}
|
||||
}
|
||||
|
||||
if obj.Range != nil {
|
||||
if err := AssertFilterOptionRangeRequired(*obj.Range); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ model_catalog_model_list.go
|
|||
model_catalog_source.go
|
||||
model_catalog_source_list.go
|
||||
model_error.go
|
||||
model_filter_option.go
|
||||
model_filter_option_range.go
|
||||
model_filter_options_list.go
|
||||
model_metadata_bool_value.go
|
||||
model_metadata_double_value.go
|
||||
model_metadata_int_value.go
|
||||
|
|
|
|||
|
|
@ -260,6 +260,136 @@ func (a *ModelCatalogServiceAPIService) FindModelsExecute(r ApiFindModelsRequest
|
|||
return localVarReturnValue, localVarHTTPResponse, nil
|
||||
}
|
||||
|
||||
type ApiFindModelsFilterOptionsRequest struct {
|
||||
ctx context.Context
|
||||
ApiService *ModelCatalogServiceAPIService
|
||||
}
|
||||
|
||||
func (r ApiFindModelsFilterOptionsRequest) Execute() (*FilterOptionsList, *http.Response, error) {
|
||||
return r.ApiService.FindModelsFilterOptionsExecute(r)
|
||||
}
|
||||
|
||||
/*
|
||||
FindModelsFilterOptions Lists fields and available options that can be used in `filterQuery` on the list models endpoint.
|
||||
|
||||
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
|
||||
@return ApiFindModelsFilterOptionsRequest
|
||||
*/
|
||||
func (a *ModelCatalogServiceAPIService) FindModelsFilterOptions(ctx context.Context) ApiFindModelsFilterOptionsRequest {
|
||||
return ApiFindModelsFilterOptionsRequest{
|
||||
ApiService: a,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
// Execute executes the request
|
||||
//
|
||||
// @return FilterOptionsList
|
||||
func (a *ModelCatalogServiceAPIService) FindModelsFilterOptionsExecute(r ApiFindModelsFilterOptionsRequest) (*FilterOptionsList, *http.Response, error) {
|
||||
var (
|
||||
localVarHTTPMethod = http.MethodGet
|
||||
localVarPostBody interface{}
|
||||
formFiles []formFile
|
||||
localVarReturnValue *FilterOptionsList
|
||||
)
|
||||
|
||||
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "ModelCatalogServiceAPIService.FindModelsFilterOptions")
|
||||
if err != nil {
|
||||
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
|
||||
}
|
||||
|
||||
localVarPath := localBasePath + "/api/model_catalog/v1alpha1/models/filter_options"
|
||||
|
||||
localVarHeaderParams := make(map[string]string)
|
||||
localVarQueryParams := url.Values{}
|
||||
localVarFormParams := url.Values{}
|
||||
|
||||
// to determine the Content-Type header
|
||||
localVarHTTPContentTypes := []string{}
|
||||
|
||||
// set Content-Type header
|
||||
localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
|
||||
if localVarHTTPContentType != "" {
|
||||
localVarHeaderParams["Content-Type"] = localVarHTTPContentType
|
||||
}
|
||||
|
||||
// to determine the Accept header
|
||||
localVarHTTPHeaderAccepts := []string{"application/json"}
|
||||
|
||||
// set Accept header
|
||||
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
|
||||
if localVarHTTPHeaderAccept != "" {
|
||||
localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
|
||||
}
|
||||
req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
|
||||
if err != nil {
|
||||
return localVarReturnValue, nil, err
|
||||
}
|
||||
|
||||
localVarHTTPResponse, err := a.client.callAPI(req)
|
||||
if err != nil || localVarHTTPResponse == nil {
|
||||
return localVarReturnValue, localVarHTTPResponse, err
|
||||
}
|
||||
|
||||
localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
|
||||
localVarHTTPResponse.Body.Close()
|
||||
localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
|
||||
if err != nil {
|
||||
return localVarReturnValue, localVarHTTPResponse, err
|
||||
}
|
||||
|
||||
if localVarHTTPResponse.StatusCode >= 300 {
|
||||
newErr := &GenericOpenAPIError{
|
||||
body: localVarBody,
|
||||
error: localVarHTTPResponse.Status,
|
||||
}
|
||||
if localVarHTTPResponse.StatusCode == 400 {
|
||||
var v Error
|
||||
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
newErr.error = err.Error()
|
||||
return localVarReturnValue, localVarHTTPResponse, newErr
|
||||
}
|
||||
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
|
||||
newErr.model = v
|
||||
return localVarReturnValue, localVarHTTPResponse, newErr
|
||||
}
|
||||
if localVarHTTPResponse.StatusCode == 401 {
|
||||
var v Error
|
||||
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
newErr.error = err.Error()
|
||||
return localVarReturnValue, localVarHTTPResponse, newErr
|
||||
}
|
||||
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
|
||||
newErr.model = v
|
||||
return localVarReturnValue, localVarHTTPResponse, newErr
|
||||
}
|
||||
if localVarHTTPResponse.StatusCode == 500 {
|
||||
var v Error
|
||||
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
newErr.error = err.Error()
|
||||
return localVarReturnValue, localVarHTTPResponse, newErr
|
||||
}
|
||||
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
|
||||
newErr.model = v
|
||||
}
|
||||
return localVarReturnValue, localVarHTTPResponse, newErr
|
||||
}
|
||||
|
||||
err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
newErr := &GenericOpenAPIError{
|
||||
body: localVarBody,
|
||||
error: err.Error(),
|
||||
}
|
||||
return localVarReturnValue, localVarHTTPResponse, newErr
|
||||
}
|
||||
|
||||
return localVarReturnValue, localVarHTTPResponse, nil
|
||||
}
|
||||
|
||||
type ApiFindSourcesRequest struct {
|
||||
ctx context.Context
|
||||
ApiService *ModelCatalogServiceAPIService
|
||||
|
|
|
|||
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
Model Catalog REST API
|
||||
|
||||
REST API for Model Registry to create and manage ML model metadata
|
||||
|
||||
API version: v1alpha1
|
||||
*/
|
||||
|
||||
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// checks if the FilterOption type satisfies the MappedNullable interface at compile time
|
||||
var _ MappedNullable = &FilterOption{}
|
||||
|
||||
// FilterOption struct for FilterOption
|
||||
type FilterOption struct {
|
||||
// The data type of the filter option
|
||||
Type string `json:"type"`
|
||||
// Known values of the property for string types with a small number of possible options.
|
||||
Values []interface{} `json:"values,omitempty"`
|
||||
Range *FilterOptionRange `json:"range,omitempty"`
|
||||
}
|
||||
|
||||
// NewFilterOption instantiates a new FilterOption object
|
||||
// This constructor will assign default values to properties that have it defined,
|
||||
// and makes sure properties required by API are set, but the set of arguments
|
||||
// will change when the set of required properties is changed
|
||||
func NewFilterOption(type_ string) *FilterOption {
|
||||
this := FilterOption{}
|
||||
this.Type = type_
|
||||
return &this
|
||||
}
|
||||
|
||||
// NewFilterOptionWithDefaults instantiates a new FilterOption object
|
||||
// This constructor will only assign default values to properties that have it defined,
|
||||
// but it doesn't guarantee that properties required by API are set
|
||||
func NewFilterOptionWithDefaults() *FilterOption {
|
||||
this := FilterOption{}
|
||||
return &this
|
||||
}
|
||||
|
||||
// GetType returns the Type field value
|
||||
func (o *FilterOption) GetType() string {
|
||||
if o == nil {
|
||||
var ret string
|
||||
return ret
|
||||
}
|
||||
|
||||
return o.Type
|
||||
}
|
||||
|
||||
// GetTypeOk returns a tuple with the Type field value
|
||||
// and a boolean to check if the value has been set.
|
||||
func (o *FilterOption) GetTypeOk() (*string, bool) {
|
||||
if o == nil {
|
||||
return nil, false
|
||||
}
|
||||
return &o.Type, true
|
||||
}
|
||||
|
||||
// SetType sets field value
|
||||
func (o *FilterOption) SetType(v string) {
|
||||
o.Type = v
|
||||
}
|
||||
|
||||
// GetValues returns the Values field value if set, zero value otherwise.
|
||||
func (o *FilterOption) GetValues() []interface{} {
|
||||
if o == nil || IsNil(o.Values) {
|
||||
var ret []interface{}
|
||||
return ret
|
||||
}
|
||||
return o.Values
|
||||
}
|
||||
|
||||
// GetValuesOk returns a tuple with the Values field value if set, nil otherwise
|
||||
// and a boolean to check if the value has been set.
|
||||
func (o *FilterOption) GetValuesOk() ([]interface{}, bool) {
|
||||
if o == nil || IsNil(o.Values) {
|
||||
return nil, false
|
||||
}
|
||||
return o.Values, true
|
||||
}
|
||||
|
||||
// HasValues returns a boolean if a field has been set.
|
||||
func (o *FilterOption) HasValues() bool {
|
||||
if o != nil && !IsNil(o.Values) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// SetValues gets a reference to the given []interface{} and assigns it to the Values field.
|
||||
func (o *FilterOption) SetValues(v []interface{}) {
|
||||
o.Values = v
|
||||
}
|
||||
|
||||
// GetRange returns the Range field value if set, zero value otherwise.
|
||||
func (o *FilterOption) GetRange() FilterOptionRange {
|
||||
if o == nil || IsNil(o.Range) {
|
||||
var ret FilterOptionRange
|
||||
return ret
|
||||
}
|
||||
return *o.Range
|
||||
}
|
||||
|
||||
// GetRangeOk returns a tuple with the Range field value if set, nil otherwise
|
||||
// and a boolean to check if the value has been set.
|
||||
func (o *FilterOption) GetRangeOk() (*FilterOptionRange, bool) {
|
||||
if o == nil || IsNil(o.Range) {
|
||||
return nil, false
|
||||
}
|
||||
return o.Range, true
|
||||
}
|
||||
|
||||
// HasRange returns a boolean if a field has been set.
|
||||
func (o *FilterOption) HasRange() bool {
|
||||
if o != nil && !IsNil(o.Range) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// SetRange gets a reference to the given FilterOptionRange and assigns it to the Range field.
|
||||
func (o *FilterOption) SetRange(v FilterOptionRange) {
|
||||
o.Range = &v
|
||||
}
|
||||
|
||||
func (o FilterOption) MarshalJSON() ([]byte, error) {
|
||||
toSerialize, err := o.ToMap()
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return json.Marshal(toSerialize)
|
||||
}
|
||||
|
||||
func (o FilterOption) ToMap() (map[string]interface{}, error) {
|
||||
toSerialize := map[string]interface{}{}
|
||||
toSerialize["type"] = o.Type
|
||||
if !IsNil(o.Values) {
|
||||
toSerialize["values"] = o.Values
|
||||
}
|
||||
if !IsNil(o.Range) {
|
||||
toSerialize["range"] = o.Range
|
||||
}
|
||||
return toSerialize, nil
|
||||
}
|
||||
|
||||
type NullableFilterOption struct {
|
||||
value *FilterOption
|
||||
isSet bool
|
||||
}
|
||||
|
||||
func (v NullableFilterOption) Get() *FilterOption {
|
||||
return v.value
|
||||
}
|
||||
|
||||
func (v *NullableFilterOption) Set(val *FilterOption) {
|
||||
v.value = val
|
||||
v.isSet = true
|
||||
}
|
||||
|
||||
func (v NullableFilterOption) IsSet() bool {
|
||||
return v.isSet
|
||||
}
|
||||
|
||||
func (v *NullableFilterOption) Unset() {
|
||||
v.value = nil
|
||||
v.isSet = false
|
||||
}
|
||||
|
||||
func NewNullableFilterOption(val *FilterOption) *NullableFilterOption {
|
||||
return &NullableFilterOption{value: val, isSet: true}
|
||||
}
|
||||
|
||||
func (v NullableFilterOption) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(v.value)
|
||||
}
|
||||
|
||||
func (v *NullableFilterOption) UnmarshalJSON(src []byte) error {
|
||||
v.isSet = true
|
||||
return json.Unmarshal(src, &v.value)
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
Model Catalog REST API
|
||||
|
||||
REST API for Model Registry to create and manage ML model metadata
|
||||
|
||||
API version: v1alpha1
|
||||
*/
|
||||
|
||||
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// checks if the FilterOptionRange type satisfies the MappedNullable interface at compile time
|
||||
var _ MappedNullable = &FilterOptionRange{}
|
||||
|
||||
// FilterOptionRange Min and max values for number types.
|
||||
type FilterOptionRange struct {
|
||||
Min *float32 `json:"min,omitempty"`
|
||||
Max *float32 `json:"max,omitempty"`
|
||||
}
|
||||
|
||||
// NewFilterOptionRange instantiates a new FilterOptionRange object
|
||||
// This constructor will assign default values to properties that have it defined,
|
||||
// and makes sure properties required by API are set, but the set of arguments
|
||||
// will change when the set of required properties is changed
|
||||
func NewFilterOptionRange() *FilterOptionRange {
|
||||
this := FilterOptionRange{}
|
||||
return &this
|
||||
}
|
||||
|
||||
// NewFilterOptionRangeWithDefaults instantiates a new FilterOptionRange object
|
||||
// This constructor will only assign default values to properties that have it defined,
|
||||
// but it doesn't guarantee that properties required by API are set
|
||||
func NewFilterOptionRangeWithDefaults() *FilterOptionRange {
|
||||
this := FilterOptionRange{}
|
||||
return &this
|
||||
}
|
||||
|
||||
// GetMin returns the Min field value if set, zero value otherwise.
|
||||
func (o *FilterOptionRange) GetMin() float32 {
|
||||
if o == nil || IsNil(o.Min) {
|
||||
var ret float32
|
||||
return ret
|
||||
}
|
||||
return *o.Min
|
||||
}
|
||||
|
||||
// GetMinOk returns a tuple with the Min field value if set, nil otherwise
|
||||
// and a boolean to check if the value has been set.
|
||||
func (o *FilterOptionRange) GetMinOk() (*float32, bool) {
|
||||
if o == nil || IsNil(o.Min) {
|
||||
return nil, false
|
||||
}
|
||||
return o.Min, true
|
||||
}
|
||||
|
||||
// HasMin returns a boolean if a field has been set.
|
||||
func (o *FilterOptionRange) HasMin() bool {
|
||||
if o != nil && !IsNil(o.Min) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// SetMin gets a reference to the given float32 and assigns it to the Min field.
|
||||
func (o *FilterOptionRange) SetMin(v float32) {
|
||||
o.Min = &v
|
||||
}
|
||||
|
||||
// GetMax returns the Max field value if set, zero value otherwise.
|
||||
func (o *FilterOptionRange) GetMax() float32 {
|
||||
if o == nil || IsNil(o.Max) {
|
||||
var ret float32
|
||||
return ret
|
||||
}
|
||||
return *o.Max
|
||||
}
|
||||
|
||||
// GetMaxOk returns a tuple with the Max field value if set, nil otherwise
|
||||
// and a boolean to check if the value has been set.
|
||||
func (o *FilterOptionRange) GetMaxOk() (*float32, bool) {
|
||||
if o == nil || IsNil(o.Max) {
|
||||
return nil, false
|
||||
}
|
||||
return o.Max, true
|
||||
}
|
||||
|
||||
// HasMax returns a boolean if a field has been set.
|
||||
func (o *FilterOptionRange) HasMax() bool {
|
||||
if o != nil && !IsNil(o.Max) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// SetMax gets a reference to the given float32 and assigns it to the Max field.
|
||||
func (o *FilterOptionRange) SetMax(v float32) {
|
||||
o.Max = &v
|
||||
}
|
||||
|
||||
func (o FilterOptionRange) MarshalJSON() ([]byte, error) {
|
||||
toSerialize, err := o.ToMap()
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return json.Marshal(toSerialize)
|
||||
}
|
||||
|
||||
func (o FilterOptionRange) ToMap() (map[string]interface{}, error) {
|
||||
toSerialize := map[string]interface{}{}
|
||||
if !IsNil(o.Min) {
|
||||
toSerialize["min"] = o.Min
|
||||
}
|
||||
if !IsNil(o.Max) {
|
||||
toSerialize["max"] = o.Max
|
||||
}
|
||||
return toSerialize, nil
|
||||
}
|
||||
|
||||
type NullableFilterOptionRange struct {
|
||||
value *FilterOptionRange
|
||||
isSet bool
|
||||
}
|
||||
|
||||
func (v NullableFilterOptionRange) Get() *FilterOptionRange {
|
||||
return v.value
|
||||
}
|
||||
|
||||
func (v *NullableFilterOptionRange) Set(val *FilterOptionRange) {
|
||||
v.value = val
|
||||
v.isSet = true
|
||||
}
|
||||
|
||||
func (v NullableFilterOptionRange) IsSet() bool {
|
||||
return v.isSet
|
||||
}
|
||||
|
||||
func (v *NullableFilterOptionRange) Unset() {
|
||||
v.value = nil
|
||||
v.isSet = false
|
||||
}
|
||||
|
||||
func NewNullableFilterOptionRange(val *FilterOptionRange) *NullableFilterOptionRange {
|
||||
return &NullableFilterOptionRange{value: val, isSet: true}
|
||||
}
|
||||
|
||||
func (v NullableFilterOptionRange) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(v.value)
|
||||
}
|
||||
|
||||
func (v *NullableFilterOptionRange) UnmarshalJSON(src []byte) error {
|
||||
v.isSet = true
|
||||
return json.Unmarshal(src, &v.value)
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
Model Catalog REST API
|
||||
|
||||
REST API for Model Registry to create and manage ML model metadata
|
||||
|
||||
API version: v1alpha1
|
||||
*/
|
||||
|
||||
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// checks if the FilterOptionsList type satisfies the MappedNullable interface at compile time
|
||||
var _ MappedNullable = &FilterOptionsList{}
|
||||
|
||||
// FilterOptionsList List of FilterOptions
|
||||
type FilterOptionsList struct {
|
||||
// A single filter option.
|
||||
Filters *map[string]FilterOption `json:"filters,omitempty"`
|
||||
}
|
||||
|
||||
// NewFilterOptionsList instantiates a new FilterOptionsList object
|
||||
// This constructor will assign default values to properties that have it defined,
|
||||
// and makes sure properties required by API are set, but the set of arguments
|
||||
// will change when the set of required properties is changed
|
||||
func NewFilterOptionsList() *FilterOptionsList {
|
||||
this := FilterOptionsList{}
|
||||
return &this
|
||||
}
|
||||
|
||||
// NewFilterOptionsListWithDefaults instantiates a new FilterOptionsList object
|
||||
// This constructor will only assign default values to properties that have it defined,
|
||||
// but it doesn't guarantee that properties required by API are set
|
||||
func NewFilterOptionsListWithDefaults() *FilterOptionsList {
|
||||
this := FilterOptionsList{}
|
||||
return &this
|
||||
}
|
||||
|
||||
// GetFilters returns the Filters field value if set, zero value otherwise.
|
||||
func (o *FilterOptionsList) GetFilters() map[string]FilterOption {
|
||||
if o == nil || IsNil(o.Filters) {
|
||||
var ret map[string]FilterOption
|
||||
return ret
|
||||
}
|
||||
return *o.Filters
|
||||
}
|
||||
|
||||
// GetFiltersOk returns a tuple with the Filters field value if set, nil otherwise
|
||||
// and a boolean to check if the value has been set.
|
||||
func (o *FilterOptionsList) GetFiltersOk() (*map[string]FilterOption, bool) {
|
||||
if o == nil || IsNil(o.Filters) {
|
||||
return nil, false
|
||||
}
|
||||
return o.Filters, true
|
||||
}
|
||||
|
||||
// HasFilters returns a boolean if a field has been set.
|
||||
func (o *FilterOptionsList) HasFilters() bool {
|
||||
if o != nil && !IsNil(o.Filters) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// SetFilters gets a reference to the given map[string]FilterOption and assigns it to the Filters field.
|
||||
func (o *FilterOptionsList) SetFilters(v map[string]FilterOption) {
|
||||
o.Filters = &v
|
||||
}
|
||||
|
||||
func (o FilterOptionsList) MarshalJSON() ([]byte, error) {
|
||||
toSerialize, err := o.ToMap()
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return json.Marshal(toSerialize)
|
||||
}
|
||||
|
||||
func (o FilterOptionsList) ToMap() (map[string]interface{}, error) {
|
||||
toSerialize := map[string]interface{}{}
|
||||
if !IsNil(o.Filters) {
|
||||
toSerialize["filters"] = o.Filters
|
||||
}
|
||||
return toSerialize, nil
|
||||
}
|
||||
|
||||
type NullableFilterOptionsList struct {
|
||||
value *FilterOptionsList
|
||||
isSet bool
|
||||
}
|
||||
|
||||
func (v NullableFilterOptionsList) Get() *FilterOptionsList {
|
||||
return v.value
|
||||
}
|
||||
|
||||
func (v *NullableFilterOptionsList) Set(val *FilterOptionsList) {
|
||||
v.value = val
|
||||
v.isSet = true
|
||||
}
|
||||
|
||||
func (v NullableFilterOptionsList) IsSet() bool {
|
||||
return v.isSet
|
||||
}
|
||||
|
||||
func (v *NullableFilterOptionsList) Unset() {
|
||||
v.value = nil
|
||||
v.isSet = false
|
||||
}
|
||||
|
||||
func NewNullableFilterOptionsList(val *FilterOptionsList) *NullableFilterOptionsList {
|
||||
return &NullableFilterOptionsList{value: val, isSet: true}
|
||||
}
|
||||
|
||||
func (v NullableFilterOptionsList) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(v.value)
|
||||
}
|
||||
|
||||
func (v *NullableFilterOptionsList) UnmarshalJSON(src []byte) error {
|
||||
v.isSet = true
|
||||
return json.Unmarshal(src, &v.value)
|
||||
}
|
||||
|
|
@ -7,7 +7,9 @@ ASSERT_FILE_PATH="$1/type_asserts.go"
|
|||
PROJECT_ROOT=$(realpath "$(dirname "$0")"/..)
|
||||
|
||||
# These files generate with incorrect logic:
|
||||
rm -f "$1/model_metadata_value.go" "$1/model_catalog_artifact.go"
|
||||
rm -f "$1/model_metadata_value.go" \
|
||||
"$1/model_catalog_artifact.go" \
|
||||
"$1/model_filter_option.go"
|
||||
|
||||
python3 "${PROJECT_ROOT}/scripts/gen_type_asserts.py" $1 >"$ASSERT_FILE_PATH"
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue