Skip to content

Commit ea5fe29

Browse files
committed
Implementing registered_model service
1 parent 50004ce commit ea5fe29

File tree

10 files changed

+257
-5
lines changed

10 files changed

+257
-5
lines changed

docs/make.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ makedocs(;
1010
"Experiment operations" => "reference/experiment.md",
1111
"Logging operations" => "reference/loggers.md",
1212
"Miscellaneous operations" => "reference/misc.md",
13-
"Run operations" => "reference/run.md"]])
13+
"Run operations" => "reference/run.md",
14+
"Registered model operations" => "reference/registered_model.md"]])
1415

1516
deploydocs(; repo="github.com/JuliaAI/MLFlowClient.jl", devbranch="main")
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Registered model operations
2+
```@docs
3+
createregisteredmodel
4+
getregisteredmodel
5+
renameregisteredmodel
6+
updateregisteredmodel
7+
deleteregisteredmodel
8+
```

src/MLFlowClient.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,8 @@ export listartifacts
6666
include("services/misc.jl")
6767
export refresh, getmetrichistory
6868

69+
include("services/registered_model.jl")
70+
export getregisteredmodel, createregisteredmodel, deleteregisteredmodel,
71+
renameregisteredmodel, updateregisteredmodel
72+
6973
end

src/api.jl

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,48 @@ function mlfpost(mlf, endpoint; kwargs...)
6666
throw(ErrorException(error_message))
6767
end
6868
end
69+
70+
"""
71+
mlfpatch(mlf, endpoint; kwargs...)
72+
73+
Performs a HTTP PATCH to the specified endpoint. kwargs are converted to JSON and become
74+
the PATCH body.
75+
"""
76+
function mlfpatch(mlf, endpoint; kwargs...)
77+
apiuri = uri(mlf, endpoint;)
78+
apiheaders = headers(mlf, Dict("Content-Type" => "application/json"))
79+
body = JSON.json(kwargs)
80+
81+
try
82+
response = HTTP.patch(apiuri, apiheaders, body)
83+
return response.body |> String |> JSON.parse
84+
catch e
85+
error_response = e.response.body |> String |> JSON.parse
86+
error_message = "$(error_response["error_code"]) - $(error_response["message"])"
87+
@error error_message
88+
throw(ErrorException(error_message))
89+
end
90+
end
91+
92+
"""
93+
mlfdelete(mlf, endpoint; kwargs...)
94+
95+
Performs a HTTP DELETE to the specified endpoint. kwargs are converted to JSON and become
96+
the DELETE body.
97+
"""
98+
function mlfdelete(mlf, endpoint; kwargs...)
99+
apiuri = uri(mlf, endpoint;
100+
parameters=Dict(k => v for (k, v) in kwargs if v !== missing))
101+
apiheaders = headers(mlf, Dict("Content-Type" => "application/json"))
102+
body = JSON.json(kwargs)
103+
104+
try
105+
response = HTTP.delete(apiuri, apiheaders, body)
106+
return response.body |> String |> JSON.parse
107+
catch e
108+
error_response = e.response.body |> String |> JSON.parse
109+
error_message = "$(error_response["error_code"]) - $(error_response["message"])"
110+
@error error_message
111+
throw(ErrorException(error_message))
112+
end
113+
end

src/services/registered_model.jl

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
"""
2+
createregisteredmodel(instance::MLFlow, name::String;
3+
tags::MLFlowUpsertData{Tag}=Tag[], description::Union{String, Missing}=missing)
4+
5+
Create a [`RegisteredModel`](@ref) with a name. Returns the newly created
6+
[`RegisteredModel`](@ref). Validates that another [`RegisteredModel`](@ref) with the same
7+
name does not already exist and fails if another [`RegisteredModel`](@ref) with the same
8+
name already exists.
9+
10+
# Arguments
11+
- `instance`: [`MLFlow`](@ref) configuration.
12+
- `name`: Register models under this name.
13+
- `tags`: A collection of [`Tag`](@ref).
14+
- `description`: Optional description for [`RegisteredModel`](@ref).
15+
16+
# Returns
17+
An instance of type [`RegisteredModel`](@ref).
18+
"""
19+
function createregisteredmodel(instance::MLFlow, name::String;
20+
tags::MLFlowUpsertData{Tag}=Tag[],
21+
description::Union{String, Missing}=missing)::RegisteredModel
22+
result = mlfpost(instance, "registered-models/create"; name=name,
23+
tags=parse(Tag, tags), description=description)
24+
return result["registered_model"] |> RegisteredModel
25+
end
26+
27+
"""
28+
getregisteredmodel(instance::MLFlow, name::String)
29+
30+
# Arguments
31+
- `instance`: [`MLFlow`](@ref) configuration.
32+
- `name`: [`RegisteredModel`](@ref) model unique name identifier.
33+
34+
# Returns
35+
An instance of type [`RegisteredModel`](@ref).
36+
"""
37+
function getregisteredmodel(instance::MLFlow, name::String)::RegisteredModel
38+
result = mlfget(instance, "registered-models/get"; name=name)
39+
return result["registered_model"] |> RegisteredModel
40+
end
41+
42+
"""
43+
renameregisteredmodel(instance::MLFlow, name::String, new_name::String)
44+
45+
# Arguments
46+
- `instance`: [`MLFlow`](@ref) configuration.
47+
- `name`: [`RegisteredModel`](@ref) unique name identifier.
48+
- `new_name`: If provided, updates the name for this [`RegisteredModel`](@ref).
49+
50+
# Returns
51+
An instance of type [`RegisteredModel`](@ref).
52+
"""
53+
function renameregisteredmodel(instance::MLFlow, name::String,
54+
new_name::String)::RegisteredModel
55+
result = mlfpost(instance, "registered-models/rename"; name=name, new_name=new_name)
56+
return result["registered_model"] |> RegisteredModel
57+
end
58+
59+
"""
60+
updateregisteredmodel(instance::MLFlow, name::String;
61+
description::Union{String, Missing}=missing)
62+
63+
# Arguments
64+
- `instance`: [`MLFlow`](@ref) configuration.
65+
- `name`: [`RegisteredModel`](@ref) unique name identifier.
66+
- `description`: If provided, updates the description for this [`RegisteredModel`](@ref).
67+
68+
# Returns
69+
An instance of type [`RegisteredModel`](@ref).
70+
"""
71+
function updateregisteredmodel(instance::MLFlow, name::String;
72+
description::Union{String, Missing}=missing)::RegisteredModel
73+
result = mlfpatch(instance, "registered-models/update"; name=name,
74+
description=description)
75+
return result["registered_model"] |> RegisteredModel
76+
end
77+
78+
"""
79+
deleteregisteredmodel(instance::MLFlow, name::String)
80+
81+
# Arguments
82+
- `instance`: [`MLFlow`](@ref) configuration.
83+
- `name`: [`RegisteredModel`](@ref) unique name identifier.
84+
85+
# Returns
86+
`true` if successful. Otherwise, raises exception.
87+
"""
88+
function deleteregisteredmodel(instance::MLFlow, name::String)::Bool
89+
mlfdelete(instance, "registered-models/delete"; name=name)
90+
return true
91+
end

src/types/enums.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
FAILED_REGISTRATION=2
1313
READY=3
1414
end
15+
ModelVersionStatus(status::String) = Dict(value => key for (key, value) in ModelVersionStatus |> Base.Enums.namemap)[status |> Symbol] |> ModelVersionStatus
1516

1617
"""
1718
RunStatus

src/types/model_version.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,10 @@ struct ModelVersion
3838
run_link::String
3939
aliases::Array{String}
4040
end
41+
ModelVersion(data::Dict{String, Any}) = ModelVersion(data["name"], data["version"],
42+
data["creation_timestamp"], data["last_updated_timestamp"], data["user_id"],
43+
data["current_stage"], data["description"], data["source"], data["run_id"],
44+
ModelVersionStatus(data["status"]), data["status_message"],
45+
[Tag(tag) for tag in get(data, "tags", [])], data["run_link"],
46+
get(data, "aliases", []))
4147
Base.show(io::IO, t::ModelVersion) = show(io, ShowCase(t, new_lines=true))

src/types/registered_model.jl

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ struct RegisteredModelAlias
1111
alias::String
1212
version::String
1313
end
14+
RegisteredModelAlias(data::Dict{String, Any}) = RegisteredModelAlias(data["alias"],
15+
data["version"])
1416
Base.show(io::IO, t::RegisteredModelAlias) = show(io, ShowCase(t, new_lines=true))
1517

1618
"""
@@ -21,8 +23,8 @@ Base.show(io::IO, t::RegisteredModelAlias) = show(io, ShowCase(t, new_lines=true
2123
- `creation_timestamp::Int64`: Timestamp recorded when this RegisteredModel was created.
2224
- `last_updated_timestamp::Int64`: Timestamp recorded when metadata for this
2325
RegisteredModel was last updated.
24-
- `user_id::String`: User that created this RegisteredModel.
25-
- `description::String`: Description of this RegisteredModel.
26+
- `user_id::Union{String, Nothing}`: User that created this RegisteredModel.
27+
- `description::Union{String, Nothing}`: Description of this RegisteredModel.
2628
- `latest_versions::Array{ModelVersion}`: Collection of latest model versions for each
2729
stage. Only contains models with current READY status.
2830
- `tags::Array{Tag}`: Additional metadata key-value pairs.
@@ -33,10 +35,16 @@ struct RegisteredModel
3335
name::String
3436
creation_timestamp::Int64
3537
last_updated_timestamp::Int64
36-
user_id::String
37-
description::String
38+
user_id::Union{String, Nothing}
39+
description::Union{String, Nothing}
3840
latest_versions::Array{ModelVersion}
3941
tags::Array{Tag}
4042
aliases::Array{RegisteredModelAlias}
4143
end
44+
RegisteredModel(data::Dict{String, Any}) = RegisteredModel(data["name"],
45+
data["creation_timestamp"], data["last_updated_timestamp"],
46+
get(data, "user_id", nothing), get(data, "description", nothing),
47+
[ModelVersion(version) for version in get(data, "latest_versions", [])],
48+
[Tag(tag) for tag in get(data, "tags", [])],
49+
[RegisteredModelAlias(alias) for alias in get(data, "aliases", [])])
4250
Base.show(io::IO, t::RegisteredModel) = show(io, ShowCase(t, new_lines=true))

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ include("services/misc.jl")
99
include("services/loggers.jl")
1010
include("services/artifact.jl")
1111
include("services/experiment.jl")
12+
include("services/registered_model.jl")

test/services/registered_model.jl

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
@testset verbose = true "create registered model" begin
2+
@ensuremlf
3+
4+
@testset "base" begin
5+
registered_model = createregisteredmodel(mlf, "missy"; description="gala")
6+
7+
@test registered_model isa RegisteredModel
8+
@test registered_model.name == "missy"
9+
@test registered_model.description == "gala"
10+
end
11+
12+
@testset "name exists" begin
13+
registered_model = getregisteredmodel(mlf, "missy")
14+
@test_throws ErrorException createregisteredmodel(mlf, registered_model.name)
15+
deleteregisteredmodel(mlf, "missy")
16+
end
17+
18+
@testset "with tags as array of tags" begin
19+
createregisteredmodel(mlf, "missy"; tags=[Tag("test_key", "test_value")])
20+
deleteregisteredmodel(mlf, "missy")
21+
end
22+
23+
@testset "with tags as array of pairs" begin
24+
createregisteredmodel(mlf, "missy"; tags=["test_key" => "test_value"])
25+
deleteregisteredmodel(mlf, "missy")
26+
end
27+
28+
@testset "with tags as array of dicts" begin
29+
createregisteredmodel(mlf, "missy";
30+
tags=[Dict("key" => "test_key", "value" => "test_value")])
31+
deleteregisteredmodel(mlf, "missy")
32+
end
33+
34+
@testset "with tags as dict" begin
35+
createregisteredmodel(mlf, "missy"; tags=Dict("test_key" => "test_value"))
36+
deleteregisteredmodel(mlf, "missy")
37+
end
38+
end
39+
40+
@testset verbose = true "get registered model" begin
41+
@ensuremlf
42+
43+
registered_model = createregisteredmodel(mlf, "missy"; description="gala")
44+
retrieved_registered_model = getregisteredmodel(mlf, registered_model.name)
45+
46+
@test retrieved_registered_model isa RegisteredModel
47+
@test retrieved_registered_model.name == registered_model.name
48+
@test retrieved_registered_model.description == registered_model.description
49+
50+
deleteregisteredmodel(mlf, "missy")
51+
end
52+
53+
@testset verbose = true "rename registered model" begin
54+
@ensuremlf
55+
56+
registered_model = createregisteredmodel(mlf, "missy"; description="gala")
57+
renamed_registered_model = renameregisteredmodel(mlf, registered_model.name, "mister")
58+
59+
@test renamed_registered_model isa RegisteredModel
60+
@test renamed_registered_model.name == "mister"
61+
@test renamed_registered_model.description == registered_model.description
62+
63+
deleteregisteredmodel(mlf, "mister")
64+
end
65+
66+
@testset verbose = true "update registered model" begin
67+
@ensuremlf
68+
69+
registered_model = createregisteredmodel(mlf, "missy"; description="gala")
70+
updated_registered_model = updateregisteredmodel(mlf, registered_model.name;
71+
description="ana")
72+
73+
@test updated_registered_model isa RegisteredModel
74+
@test updated_registered_model.name == registered_model.name
75+
@test updated_registered_model.description == "ana"
76+
77+
deleteregisteredmodel(mlf, "missy")
78+
end
79+
80+
@testset verbose = true "delete registered model" begin
81+
@ensuremlf
82+
83+
registered_model = createregisteredmodel(mlf, "missy"; description="gala")
84+
deleteregisteredmodel(mlf, "missy")
85+
86+
@test_throws ErrorException getregisteredmodel(mlf, "missy")
87+
end

0 commit comments

Comments
 (0)