Skip to content

Commit

Permalink
Fix version patch and post
Browse files Browse the repository at this point in the history
  • Loading branch information
tiopramayudi committed Jan 25, 2024
1 parent 9dfff73 commit ebce098
Show file tree
Hide file tree
Showing 8 changed files with 380 additions and 10 deletions.
1 change: 1 addition & 0 deletions api/api/versions_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ func (c *VersionsController) CreateVersion(r *http.Request, vars map[string]stri
ArtifactURI: run.Info.ArtifactURI,
Labels: versionPost.Labels,
PythonVersion: versionPost.PythonVersion,
ModelSchema: versionPost.ModelSchema,
}

version, _ = c.VersionsService.Save(ctx, version, c.FeatureToggleConfig.MonitoringConfig)
Expand Down
316 changes: 316 additions & 0 deletions api/api/versions_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,170 @@ func TestPatchVersion(t *testing.T) {
data: Error{Message: "Error patching model version: Error creating secret: db is down"},
},
},
{
desc: "Should success update model schema",
vars: map[string]string{
"model_id": "1",
"version_id": "1",
},
requestBody: &models.VersionPatch{
Properties: &models.KV{
"name": "model-1",
"created_by": "anonymous",
},
ModelSchema: &models.ModelSchema{
Spec: &models.SchemaSpec{
PredictionIDColumn: "prediction_id",
ModelPredictionOutput: &models.ModelPredictionOutput{
RankingOutput: &models.RankingOutput{
PredictionGroudIDColumn: "session_id",
RankScoreColumn: "score",
RelevanceScoreColumn: "relevance_score",
OutputClass: models.Ranking,
},
},
FeatureTypes: map[string]models.ValueType{
"featureA": models.Float64,
"featureB": models.Int64,
"featureC": models.Boolean,
},
},
ModelID: models.ID(1),
},
},
versionService: func() *mocks.VersionsService {
svc := &mocks.VersionsService{}
svc.On("FindByID", mock.Anything, models.ID(1), models.ID(1), mock.Anything).Return(
&models.Version{
ID: models.ID(1),
ModelID: models.ID(1),
Model: &models.Model{
ID: models.ID(1),
Name: "model-1",
ProjectID: models.ID(1),
Project: mlp.Project{},
ExperimentID: 1,
Type: "pyfunc",
MlflowURL: "http://mlflow.com",
},
MlflowURL: "http://mlflow.com",
}, nil)
svc.On("Save", mock.Anything, &models.Version{
ID: models.ID(1),
ModelID: models.ID(1),
Model: &models.Model{
ID: models.ID(1),
Name: "model-1",
ProjectID: models.ID(1),
Project: mlp.Project{},
ExperimentID: 1,
Type: "pyfunc",
MlflowURL: "http://mlflow.com",
},
MlflowURL: "http://mlflow.com",
Properties: models.KV{
"name": "model-1",
"created_by": "anonymous",
},
ModelSchema: &models.ModelSchema{
Spec: &models.SchemaSpec{
PredictionIDColumn: "prediction_id",
ModelPredictionOutput: &models.ModelPredictionOutput{
RankingOutput: &models.RankingOutput{
PredictionGroudIDColumn: "session_id",
RankScoreColumn: "score",
RelevanceScoreColumn: "relevance_score",
OutputClass: models.Ranking,
},
},
FeatureTypes: map[string]models.ValueType{
"featureA": models.Float64,
"featureB": models.Int64,
"featureC": models.Boolean,
},
},
ModelID: models.ID(1),
},
}, mock.Anything).Return(&models.Version{
ID: models.ID(1),
ModelID: models.ID(1),
Model: &models.Model{
ID: models.ID(1),
Name: "model-1",
ProjectID: models.ID(1),
Project: mlp.Project{},
ExperimentID: 1,
Type: "pyfunc",
MlflowURL: "http://mlflow.com",
},
MlflowURL: "http://mlflow.com",
Properties: models.KV{
"name": "model-1",
"created_by": "anonymous",
},
ModelSchema: &models.ModelSchema{
Spec: &models.SchemaSpec{
PredictionIDColumn: "prediction_id",
ModelPredictionOutput: &models.ModelPredictionOutput{
RankingOutput: &models.RankingOutput{
PredictionGroudIDColumn: "session_id",
RankScoreColumn: "score",
RelevanceScoreColumn: "relevance_score",
OutputClass: models.Ranking,
},
},
FeatureTypes: map[string]models.ValueType{
"featureA": models.Float64,
"featureB": models.Int64,
"featureC": models.Boolean,
},
},
ModelID: models.ID(1),
},
}, nil)
return svc
},
expected: &Response{
code: http.StatusOK,
data: &models.Version{
ID: models.ID(1),
ModelID: models.ID(1),
Model: &models.Model{
ID: models.ID(1),
Name: "model-1",
ProjectID: models.ID(1),
Project: mlp.Project{},
ExperimentID: 1,
Type: "pyfunc",
MlflowURL: "http://mlflow.com",
},
MlflowURL: "http://mlflow.com",
Properties: models.KV{
"name": "model-1",
"created_by": "anonymous",
},
ModelSchema: &models.ModelSchema{
Spec: &models.SchemaSpec{
PredictionIDColumn: "prediction_id",
ModelPredictionOutput: &models.ModelPredictionOutput{
RankingOutput: &models.RankingOutput{
PredictionGroudIDColumn: "session_id",
RankScoreColumn: "score",
RelevanceScoreColumn: "relevance_score",
OutputClass: models.Ranking,
},
},
FeatureTypes: map[string]models.ValueType{
"featureA": models.Float64,
"featureB": models.Int64,
"featureC": models.Boolean,
},
},
ModelID: models.ID(1),
},
},
},
},
}
for _, tC := range testCases {
t.Run(tC.desc, func(t *testing.T) {
Expand Down Expand Up @@ -1155,6 +1319,158 @@ func TestCreateVersion(t *testing.T) {
},
},
},
{
desc: "Should successfully create version with model schema",
vars: map[string]string{
"model_id": "1",
},
body: models.VersionPost{
ModelSchema: &models.ModelSchema{
Spec: &models.SchemaSpec{
PredictionIDColumn: "prediction_id",
ModelPredictionOutput: &models.ModelPredictionOutput{
RankingOutput: &models.RankingOutput{
PredictionGroudIDColumn: "session_id",
RankScoreColumn: "score",
RelevanceScoreColumn: "relevance_score",
OutputClass: models.Ranking,
},
},
FeatureTypes: map[string]models.ValueType{
"featureA": models.Float64,
"featureB": models.Int64,
"featureC": models.Boolean,
},
},
ModelID: models.ID(1),
},
},
modelsService: func() *mocks.ModelsService {
svc := &mocks.ModelsService{}
svc.On("FindByID", mock.Anything, models.ID(1)).Return(&models.Model{
ID: models.ID(1),
Name: "model-1",
ProjectID: models.ID(1),
Project: mlp.Project{
MLFlowTrackingURL: "http://www.notinuse.com",
},
ExperimentID: 1,
Type: "pyfunc",
MlflowURL: "http://mlflow.com",
Endpoints: nil,
}, nil)
return svc
},
mlflowClient: func() *mlfmocks.Client {
svc := &mlfmocks.Client{}
svc.On("CreateRun", "1").Return(&mlflow.Run{
Info: mlflow.Info{
RunID: "1",
ArtifactURI: "artifact/url/run",
},
}, nil)
return svc
},
versionService: func() *mocks.VersionsService {
svc := &mocks.VersionsService{}
svc.On("Save", mock.Anything, &models.Version{
ModelID: models.ID(1),
RunID: "1",
ArtifactURI: "artifact/url/run",
PythonVersion: DEFAULT_PYTHON_VERSION,
ModelSchema: &models.ModelSchema{
Spec: &models.SchemaSpec{
PredictionIDColumn: "prediction_id",
ModelPredictionOutput: &models.ModelPredictionOutput{
RankingOutput: &models.RankingOutput{
PredictionGroudIDColumn: "session_id",
RankScoreColumn: "score",
RelevanceScoreColumn: "relevance_score",
OutputClass: models.Ranking,
},
},
FeatureTypes: map[string]models.ValueType{
"featureA": models.Float64,
"featureB": models.Int64,
"featureC": models.Boolean,
},
},
ModelID: models.ID(1),
},
}, mock.Anything).Return(&models.Version{
ID: models.ID(1),
ModelID: models.ID(1),
Model: &models.Model{
ID: models.ID(1),
Name: "model-1",
ProjectID: models.ID(1),
Project: mlp.Project{},
ExperimentID: 1,
Type: "sklearn",
MlflowURL: "http://mlflow.com",
},
MlflowURL: "http://mlflow.com",
PythonVersion: DEFAULT_PYTHON_VERSION,
ModelSchema: &models.ModelSchema{
Spec: &models.SchemaSpec{
PredictionIDColumn: "prediction_id",
ModelPredictionOutput: &models.ModelPredictionOutput{
RankingOutput: &models.RankingOutput{
PredictionGroudIDColumn: "session_id",
RankScoreColumn: "score",
RelevanceScoreColumn: "relevance_score",
OutputClass: models.Ranking,
},
},
FeatureTypes: map[string]models.ValueType{
"featureA": models.Float64,
"featureB": models.Int64,
"featureC": models.Boolean,
},
},
ModelID: models.ID(1),
},
}, nil)
return svc
},
expected: &Response{
code: http.StatusCreated,
data: &models.Version{
ID: models.ID(1),
ModelID: models.ID(1),
Model: &models.Model{
ID: models.ID(1),
Name: "model-1",
ProjectID: models.ID(1),
Project: mlp.Project{},
ExperimentID: 1,
Type: "sklearn",
MlflowURL: "http://mlflow.com",
},
MlflowURL: "http://mlflow.com",
PythonVersion: DEFAULT_PYTHON_VERSION,
ModelSchema: &models.ModelSchema{
Spec: &models.SchemaSpec{
PredictionIDColumn: "prediction_id",
ModelPredictionOutput: &models.ModelPredictionOutput{
RankingOutput: &models.RankingOutput{
PredictionGroudIDColumn: "session_id",
RankScoreColumn: "score",
RelevanceScoreColumn: "relevance_score",
OutputClass: models.Ranking,
},
},
FeatureTypes: map[string]models.ValueType{
"featureA": models.Float64,
"featureB": models.Int64,
"featureC": models.Boolean,
},
},
ModelID: models.ID(1),
},
},
},
},
}
for _, tC := range testCases {
t.Run(tC.desc, func(t *testing.T) {
Expand Down
10 changes: 8 additions & 2 deletions api/models/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,15 @@ type Version struct {
}

type VersionPost struct {
Labels KV `json:"labels" gorm:"labels"`
PythonVersion string `json:"python_version" gorm:"python_version"`
Labels KV `json:"labels" gorm:"labels"`
PythonVersion string `json:"python_version" gorm:"python_version"`
ModelSchema *ModelSchema `json:"model_schema"`
}

type VersionPatch struct {
Properties *KV `json:"properties,omitempty"`
CustomPredictor *CustomPredictor `json:"custom_predictor,omitempty"`
ModelSchema *ModelSchema `json:"model_schema"`
}

type CustomPredictor struct {
Expand Down Expand Up @@ -100,6 +102,10 @@ func (v *Version) Patch(patch *VersionPatch) error {
}
v.CustomPredictor = patch.CustomPredictor
}
if patch.ModelSchema != nil {
v.ModelSchema = patch.ModelSchema
}

return nil
}

Expand Down
2 changes: 2 additions & 0 deletions python/sdk/client/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ def response_deserialize(
# if not found, look for '1XX', '2XX', etc.
response_type = response_types_map.get(str(response_data.status)[0] + "XX", None)

print(f"response status ----- {response_data.status}")
if not 200 <= response_data.status <= 299:
if response_data.status == 400:
raise BadRequestException(http_resp=response_data)
Expand Down Expand Up @@ -328,6 +329,7 @@ def response_deserialize(
match = re.search(r"charset=([a-zA-Z\-\d]+)[\s;]?", content_type)
encoding = match.group(1) if match else "utf-8"
response_text = response_data.data.decode(encoding)
print(f"response_text ------ {response_text}")
return_data = self.deserialize(response_text, response_type)

return ApiResponse(
Expand Down
4 changes: 4 additions & 0 deletions python/sdk/merlin/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,10 @@ def url(self) -> str:
model_id = self.model.id
base_url = guess_mlp_ui_url(self.model.project.url)
return f"{base_url}/projects/{project_id}/models/{model_id}/versions"

@property
def model_schema(self) -> Optional[ModelSchema]:
return self._model_schema

def start(self):
"""
Expand Down
Loading

0 comments on commit ebce098

Please sign in to comment.