From c37f07935e9662716553bd5f8de33266cbf1733c Mon Sep 17 00:00:00 2001 From: Morten Piibeleht Date: Mon, 1 Dec 2025 12:10:48 +0200 Subject: [PATCH 1/4] refactor: use AbstractDict for JSON 1.x support --- .github/workflows/CI.yml | 26 ++++++++++++++++++++++++++ src/applications.jl | 12 ++++++------ src/batchimages.jl | 8 ++++---- src/datasets.jl | 18 +++++++++--------- src/jobs/jobs.jl | 8 ++++---- src/jobs/logging-kafka.jl | 22 +++++++++++----------- src/jobs/logging-legacy.jl | 10 +++++----- src/jobsubmission.jl | 16 ++++++++-------- src/userinfo.jl | 6 +++--- src/utils.jl | 16 ++++++++-------- test/datasets.jl | 2 +- test/mocking.jl | 4 ++-- 12 files changed, 87 insertions(+), 61 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f61b3dfd9..03283baee 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -68,6 +68,32 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} + test-json-021: + name: Julia 1 - ubuntu-latest - JSON 0.21 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: julia-actions/setup-julia@v2 + with: + version: "1" + - uses: actions/cache@v4 + env: + cache-name: cache-artifacts + with: + path: ~/.julia/artifacts + key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} + restore-keys: | + ${{ runner.os }}-test-${{ env.cache-name }}- + ${{ runner.os }}-test- + ${{ runner.os }}- + - name: Install dependencies with JSON 0.21 + shell: julia --color=yes --project {0} + run: | + import Pkg + Pkg.add(name="JSON", version="0.21") + Pkg.instantiate() + - uses: julia-actions/julia-runtest@v1 + aqua: name: Aqua runs-on: ubuntu-latest diff --git a/src/applications.jl b/src/applications.jl index 47b4ff3c8..82a4f1458 100644 --- a/src/applications.jl +++ b/src/applications.jl @@ -13,7 +13,7 @@ struct _RegistryInfo name::String uuid::UUIDs.UUID - function _RegistryInfo(json::Dict; var="_RegistryInfo") + function _RegistryInfo(json::AbstractDict; var="_RegistryInfo") id = _json_get(json, "id", Integer; var) name = _json_get(json, "name", AbstractString; var) uuid = _json_get(json, "uuid", UUIDs.UUID; var, parse=true) @@ -23,7 +23,7 @@ end function _api_registries(auth::Authentication)::Vector{_RegistryInfo} r = _restcall(auth, :GET, "app", "packages", "registries") - json, _ = _parse_response_json(r, Dict) + json, _ = _parse_response_json(r, AbstractDict) _json_check_success(json; var="app/packages/registries") registries = _json_get(json, "registries", Vector; var="app/packages/registries") # Note: this broadcast can return Any[] if `registries` is empty, hence @@ -78,7 +78,7 @@ struct DefaultApp <: AbstractJuliaHubApp _apptype::String _json::Dict{String, Any} - function DefaultApp(json::Dict, appargs::AbstractVector) + function DefaultApp(json::AbstractDict, appargs::AbstractVector) apptype = _json_get(json, "appType", AbstractString; var="default app") name = _json_get(json, "name", AbstractString; var="default app") new(name, appargs, apptype, json) @@ -124,7 +124,7 @@ struct PackageApp <: AbstractJuliaHubApp _registry::_RegistryInfo _json::Dict{String, Any} - function PackageApp(json::Dict, registries::Vector{_RegistryInfo}) + function PackageApp(json::AbstractDict, registries::Vector{_RegistryInfo}) name = _json_get(json, "name", AbstractString; var="registered app") uuid = _json_get(json, "uuid", UUIDs.UUID; var="registered app", parse=true) registrymap = _json_get(json, "registrymap", Vector; var="registered app") @@ -180,7 +180,7 @@ struct UserApp <: AbstractJuliaHubApp _repository::String _json::Dict{String, Any} - function UserApp(json::Dict) + function UserApp(json::AbstractDict) name = _json_get(json, "name", AbstractString; var="user app") repository_url = _json_get(json, "repourl", AbstractString; var="user app") new(name, repository_url, json) @@ -258,7 +258,7 @@ end function _api_apps_default(auth::Authentication) r = _restcall(auth, :GET, "app", "applications", "default") r.status == 200 || _throw_invalidresponse(r; msg="Unable to list default applications.") - return _parse_response_json(r, Dict) + return _parse_response_json(r, AbstractDict) end function _apps_default(auth::Authentication) diff --git a/src/batchimages.jl b/src/batchimages.jl index 8fedfff31..0b79c16c2 100644 --- a/src/batchimages.jl +++ b/src/batchimages.jl @@ -192,12 +192,12 @@ end function _api_product_image_groups(auth::Authentication) r = _restcall(auth, :GET, "juliaruncloud", "product_image_groups"; query=[("extended", "true")]) r.status == 200 || _throw_invalidresponse(r) - return _parse_response_json(r, Dict) + return _parse_response_json(r, AbstractDict) end function _product_image_groups(auth::Authentication) r_json, r_json_str = _api_product_image_groups(auth) - image_groups = _get_json(r_json, "image_groups", Dict) + image_groups = _get_json(r_json, "image_groups", AbstractDict) # Double check that the returned JSON is correct image_groups = map(collect(pairs(image_groups))) do (image_group, images) if !isa(images, Vector) @@ -227,7 +227,7 @@ function _group_images(images; image_group::AbstractString) # which should be unique. grouped_images = Dict{String, _ImageKeys}() for image in images - if !isa(image, Dict) + if !isa(image, AbstractDict) msg = """ Invalid JSON returned by the server: image value is not an object image_group = $(image_group) @@ -279,7 +279,7 @@ function _group_images(images; image_group::AbstractString) sort(grouped_images; by=((display_name, keys)::Pair) -> (!keys.isdefault, display_name)) end -function _parse_image_group_entry_type(image::Dict) +function _parse_image_group_entry_type(image::AbstractDict) image_type = _get_json(image, "type", String) m = match(r"(base|option)-(cpu|gpu)", image_type) if isnothing(m) diff --git a/src/datasets.jl b/src/datasets.jl index 7867b9295..9f9d41f35 100644 --- a/src/datasets.jl +++ b/src/datasets.jl @@ -58,7 +58,7 @@ struct DatasetVersion timestamp::TimeZones.ZonedDateTime _blobstore_path::String - function DatasetVersion(json::Dict; owner::AbstractString, name::AbstractString) + function DatasetVersion(json::AbstractDict; owner::AbstractString, name::AbstractString) msg = "Unable to parse dataset version info for ($owner, $name)" version = _get_json(json, "version", Int; msg) size = _get_json(json, "size", Int; msg) @@ -158,9 +158,9 @@ Base.@kwdef struct Dataset _json::Dict end -function Dataset(d::Dict; expected_project::Union{UUID, Nothing}=nothing) +function Dataset(d::AbstractDict; expected_project::Union{UUID, Nothing}=nothing) owner = _get_json( - _get_json(d, "owner", Dict), + _get_json(d, "owner", AbstractDict), "username", String, ) name = _get_json(d, "name", AbstractString) @@ -169,7 +169,7 @@ function Dataset(d::Dict; expected_project::Union{UUID, Nothing}=nothing) [DatasetVersion(json; owner, name) for json in versions_json]; by=dsv -> dsv.id, ) - _storage = let storage_json = _get_json(d, "storage", Dict) + _storage = let storage_json = _get_json(d, "storage", AbstractDict) _DatasetStorage(; credentials_url=_get_json(d, "credentials_url", AbstractString), region=_get_json(storage_json, "bucket_region", AbstractString), @@ -178,7 +178,7 @@ function Dataset(d::Dict; expected_project::Union{UUID, Nothing}=nothing) ) end project = if !isnothing(expected_project) - project_json = _get_json(d, "project", Dict) + project_json = _get_json(d, "project", AbstractDict) project_json_uuid = UUIDs.UUID( _get_json(project_json, "project_id", String) ) @@ -723,7 +723,7 @@ end function _check_dataset_upload_config( r::_RESTResponse, expected_dtype::AbstractString; newly_created_dataset::Bool ) - upload_config, _ = _parse_response_json(r, Dict) + upload_config, _ = _parse_response_json(r, AbstractDict) # Verify that the dtype of the remote dataset is what we expect it to be. if upload_config["dataset_type"] != expected_dtype if newly_created_dataset @@ -899,7 +899,7 @@ storage_class = ) end -function _write_rclone_config(io::IO, upload_config::Dict) +function _write_rclone_config(io::IO, upload_config::AbstractDict) region = upload_config["location"]["region"] access_key_id = upload_config["credentials"]["access_key_id"] secret_access_key = upload_config["credentials"]["secret_access_key"] @@ -914,7 +914,7 @@ end function _get_dataset_credentials(auth::Authentication, dataset::Dataset) r = @_httpcatch HTTP.get(dataset._storage.credentials_url, _authheaders(auth)) r.status == 200 || _throw_invalidresponse(r; msg="Unable get credentials for $(dataset)") - credentials, _ = _parse_response_json(r, Dict) + credentials, _ = _parse_response_json(r, AbstractDict) return credentials end @@ -1143,7 +1143,7 @@ end # Low-level internal function that just takes a dict of params, without caring # if they are valid or not, and returns the raw HTTP response. -function _update_dataset(auth::Authentication, dataset_name::AbstractString, params::Dict) +function _update_dataset(auth::Authentication, dataset_name::AbstractString, params::AbstractDict) _restcall(auth, :PATCH, ("user", "datasets", dataset_name), JSON.json(params)) end diff --git a/src/jobs/jobs.jl b/src/jobs/jobs.jl index 5c3cdd17a..70efc0b62 100644 --- a/src/jobs/jobs.jl +++ b/src/jobs/jobs.jl @@ -53,7 +53,7 @@ struct JobFile _upload_timestamp::String function JobFile(jobname::AbstractString, jf::AbstractDict; var) - filehash = let hash = _json_get(jf, "hash", Dict; var) + filehash = let hash = _json_get(jf, "hash", AbstractDict; var) hash_algorithm = _json_get(hash, "algorithm", Union{String, Nothing}; var) hash_value = _json_get(hash, "value", Union{String, Nothing}; var) if isnothing(hash_algorithm) || isnothing(hash_value) @@ -374,7 +374,7 @@ function job end function job(id::AbstractString; throw::Bool=true, auth::Authentication=__auth__()) r = _restcall(auth, :GET, "api", "rest", "jobs", id; hasura=true) r.status == 200 || _throw_invalidresponse(r) - job, json = _parse_response_json(r, Dict) + job, json = _parse_response_json(r, AbstractDict) details = get(job, "details") do Base.throw(JuliaHubError("Invalid JSON returned by the server:\n$(json)")) end @@ -559,7 +559,7 @@ kill_job(job::Job; auth::Authentication=__auth__()) = kill_job(job.id; auth) function kill_job(jobname::AbstractString; auth::Authentication=__auth__()) r = _restcall(auth, :GET, "juliaruncloud", "kill_job"; query=(; jobname=string(jobname))) if r.status == 200 - response, json = _parse_response_json(r, Dict) + response, json = _parse_response_json(r, AbstractDict) # response_json["status"] might not be a Bool if get(response, "status", false) != true throw(JuliaHubError("Unexpected JSON returned by the server\n$(json)")) @@ -601,7 +601,7 @@ function extend_job(jobname::AbstractString, extension::Limit; auth::Authenticat ) r = _restcall(auth, :POST, ("juliaruncloud", "extend_job_time_limit"), payload) if r.status == 200 - response, json = _parse_response_json(r, Dict) + response, json = _parse_response_json(r, AbstractDict) success = get(response, "success", nothing) message = get(response, "message", "") if success === true diff --git a/src/jobs/logging-kafka.jl b/src/jobs/logging-kafka.jl index b521b826a..c46d5d1c1 100644 --- a/src/jobs/logging-kafka.jl +++ b/src/jobs/logging-kafka.jl @@ -1,14 +1,14 @@ struct _KafkaLogging <: _JobLoggingAPIVersion end -function JobLogMessage(::_KafkaLogging, json::Dict) +function JobLogMessage(::_KafkaLogging, json::AbstractDict) offset = _get_json(json, "offset", Int) # The timestamps in Kafka logs are in milliseconds - value = _get_json(json, "value", Dict) + value = _get_json(json, "value", AbstractDict) timestamp = _ms_utc2localtz(_get_json(value, "timestamp", Int)) - log = _get_json(value, "log", Dict) + log = _get_json(value, "log", AbstractDict) message = _get_json(log, "message", String) - metadata = _get_json_or(log, "metadata", Dict, Dict{String, Any}()) - keywords = _get_json_or(log, "keywords", Dict, Dict{String, Any}()) + metadata::Dict = _get_json_or(log, "metadata", AbstractDict, Dict{String, Any}()) + keywords::Dict = _get_json_or(log, "keywords", AbstractDict, Dict{String, Any}()) stream = _get_json_or(log, "stream", String, nothing) JobLogMessage(; _offset=offset, timestamp, message, _metadata=metadata, _keywords=keywords, @@ -412,12 +412,12 @@ function _get_logs_kafka_parsed( @debug "_get_logs_kafka_parsed($jobname): start REST call" _taskstamp() offset consumer_id timeout r = _get_job_logs_kafka_restcall(auth, jobname; offset, consumer_id, timeout) r.status == 200 || _throw_invalidresponse(r) - json, json_str = _parse_response_json(r, Dict) + json, json_str = _parse_response_json(r, AbstractDict) consumer_id = _get_json(json, "consumer_id", Int) # If the Kafka endpoints want to return an empty list of log messages, it returns it # as an empty object (i.e. "logs": {}), rather than an empty array. - logs = _get_json(json, "logs", Union{Dict, Vector}) - logmessages, jobdone = if isa(logs, Dict) + logs = _get_json(json, "logs", Union{AbstractDict, Vector}) + logmessages, jobdone = if isa(logs, AbstractDict) if !isempty(logs) throw(JuliaHubError("Non-empty dictionary for logs\n$(json_str)")) end @@ -434,7 +434,7 @@ function _get_logs_kafka_parsed( # also occur in a random place.. or there may be multiple meta messages. bottom_message = false for (i, log) in enumerate(logs) - if !isa(log, Dict) + if !isa(log, AbstractDict) @error "Invalid log message type $(typeof(log)) (at $i / $(length(logs)); omitting)" i log continue end @@ -477,8 +477,8 @@ function _get_logs_kafka_parsed( return (; consumer_id, logs=logmessages, isdone=jobdone) end -function _kafka_is_last_message(json::Dict) - value = _get_json_or(json, "value", Dict, Dict()) +function _kafka_is_last_message(json::AbstractDict) + value::Dict = _get_json_or(json, "value", AbstractDict, Dict()) return get(value, "meta", nothing) == "bottom" end diff --git a/src/jobs/logging-legacy.jl b/src/jobs/logging-legacy.jl index c8d486d45..368a1c73b 100644 --- a/src/jobs/logging-legacy.jl +++ b/src/jobs/logging-legacy.jl @@ -1,12 +1,12 @@ struct _LegacyLogging <: _JobLoggingAPIVersion end -function JobLogMessage(::_LegacyLogging, json::Dict, offset::Integer) +function JobLogMessage(::_LegacyLogging, json::AbstractDict, offset::Integer) # The .message property _should_ always be present in the log messages, # but there are a few versions out there where it's sometimes omitted due # to a backend bug. So we default to an empty string in those cases. message = _get_json_or(json, "message", String, "") - keywords = _get_json_or(json, "keywords", Dict, Dict{String, Any}()) - metadata = _get_json_or(json, "metadata", Dict, Dict{String, Any}()) + keywords::Dict = _get_json_or(json, "keywords", AbstractDict, Dict{String, Any}()) + metadata::Dict = _get_json_or(json, "metadata", AbstractDict, Dict{String, Any}()) timestamp = if haskey(json, "timestamp") # Apparently timestamps are sometimes strings, sometimes integers.. timestamp = _get_json(json, "timestamp", Union{String, Integer}) @@ -273,7 +273,7 @@ end # The log messages (may) have special _meta messages at the start and at the end. # These have a `"_meta": true` field, and should have either `"end": "top"` (if first) # or `"end": "bottom"` (if last message). -function _log_legacy_is_meta(log::Dict, s::AbstractString) +function _log_legacy_is_meta(log::AbstractDict, s::AbstractString) haskey(log, "_meta") || return false if log["_meta"] !== true throw(JuliaHubError(""" @@ -529,7 +529,7 @@ function _job_logs_legacy_start_streaming!(auth::Authentication, buffer::_Legacy end # If the message wasn't empty, we assume that it is a valid JSON blob containing # a log message. - msg, _ = _parse_response_json(msg, Dict) + msg, _ = _parse_response_json(msg, AbstractDict) if _log_legacy_is_meta(msg, "top") @error "Unexpected `top` meta message streamed" msg return nothing diff --git a/src/jobsubmission.jl b/src/jobsubmission.jl index 9c95832d0..03ce9805d 100644 --- a/src/jobsubmission.jl +++ b/src/jobsubmission.jl @@ -43,7 +43,7 @@ struct _JobSubmission1 # User code arguments appType::Union{AbstractString, Nothing}=nothing, appArgs::Union{Dict, Nothing}=nothing, - args::Dict, + args::AbstractDict, projectid::Union{AbstractString, Nothing}, customcode::Bool, usercode::Union{AbstractString, Nothing}=nothing, projecttoml::Union{AbstractString, Nothing}=nothing, @@ -158,7 +158,7 @@ end _string_or_nothing(x) = isnothing(x) ? nothing : string(x) -function _check_key(d::Dict, key, ::Type{T}; varname) where {T} +function _check_key(d::AbstractDict, key, ::Type{T}; varname) where {T} haskey(d, key) || throw(ArgumentError("Dictionary `$varname` is missing key `$key`.")) isa(d[key], T) || throw(ArgumentError("`$varname[\"$key\"]` is not `<: $T` (got `$(typeof(d[key]))`).")) @@ -185,7 +185,7 @@ function _submit_job(auth::Authentication, j::_JobSubmission1) """ r = _restcall(auth, :POST, ("juliaruncloud", "submit_job"), HTTP.Form(params)) if r.status == 200 - r_json, _ = _parse_response_json(r, Dict) + r_json, _ = _parse_response_json(r, AbstractDict) haskey(r_json, "success") && r_json["success"] || throw(JuliaHubError( """ Invalid response JSON from JuliaHub: @@ -868,10 +868,10 @@ function _get_appbundle_upload_url(auth::Authentication, appbundle_tar_path::Abs ) r = _restcall(auth, :GET, "jobs", "appbundle_upload_url"; query=appbundle_params) r.status == 200 || _throw_invalidresponse(r; msg="Unable to upload appbundle to JuliaHub.") - r_json, _ = _parse_response_json(r, Dict) + r_json, _ = _parse_response_json(r, AbstractDict) _get_json(r_json, "success", Bool) || _throw_invalidresponse(r; msg="Unable to upload appbundle to JuliaHub.") - message = _get_json(r_json, "message", Dict) + message = _get_json(r_json, "message", AbstractDict) upload_url = _get_json(message, "upload_url", AbstractString) return upload_url, appbundle_params end @@ -914,9 +914,9 @@ struct PackageJob <: AbstractJobConfig args::Dict sysimage::Bool - PackageJob(app::PackageApp; args::Dict=Dict(), sysimage::Bool=_DEFAULT_BatchJob_sysimage) = + PackageJob(app::PackageApp; args::AbstractDict=Dict(), sysimage::Bool=_DEFAULT_BatchJob_sysimage) = new(app, app.name, app._registry.name, string(app._uuid), args, sysimage) - PackageJob(app::UserApp; args::Dict=Dict(), sysimage::Bool=_DEFAULT_BatchJob_sysimage) = + PackageJob(app::UserApp; args::AbstractDict=Dict(), sysimage::Bool=_DEFAULT_BatchJob_sysimage) = new(app, app.name, nothing, app._repository, args, sysimage) end @@ -926,7 +926,7 @@ function _check_packagebundler_dir(bundlepath::AbstractString) return nothing end -function _check_job_args(args::Dict) +function _check_job_args(args::AbstractDict) for k in keys(args) isa(k, AbstractString) || throw( diff --git a/src/userinfo.jl b/src/userinfo.jl index 5375e2e19..c36267ffc 100644 --- a/src/userinfo.jl +++ b/src/userinfo.jl @@ -41,8 +41,8 @@ function _get_authenticated_user_legacy(server::AbstractString, token::Secret):: r = Mocking.@mock _get_authenticated_user_legacy_gql_request(server, token) msg = "Unable to query for user information (Hasura)" # error message r.status == 200 || _throw_invalidresponse(r; msg) - json, _ = _parse_response_json(r, Dict) - users = _get_json(_get_json(json, "data", Dict; msg), "users", Vector) + json, _ = _parse_response_json(r, AbstractDict) + users = _get_json(_get_json(json, "data", AbstractDict; msg), "users", Vector) length(users) == 1 || throw( JuliaHubError("$msg\nInvalid JSON returned by the server: length(users)=$(length(users))") ) @@ -60,7 +60,7 @@ function _get_api_information(server::AbstractString, token::Secret)::_JuliaHubI # First, try to access the /api/v1 endpoint r = Mocking.@mock _get_authenticated_user_api_v1_request(server, token) if r.status == 200 - json, _ = _parse_response_json(r, Dict) + json, _ = _parse_response_json(r, AbstractDict) username = _get_json_or(json, "username", String, nothing) api_version = _json_get(json, "api_version", VersionNumber; parse=true, var="/api/v1") return _JuliaHubInfo(; username, api_version) diff --git a/src/utils.jl b/src/utils.jl index 8841df66c..21f017427 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -139,7 +139,7 @@ end # A function to throw a consistent error message when the backend happens to return # invalid JSON. # -# Returns the (parsed_json::Dict/Vector/..., json_string::String) tuple. The second +# Returns the (parsed_json::AbstractDict/Vector/..., json_string::String) tuple. The second # element would generally be ignored, but can be use to print useful error messages # in the callee. function _parse_response_json(r::HTTP.Response, ::Type{T})::Tuple{T, String} where {T} @@ -167,7 +167,7 @@ function _parse_response_json(s::AbstractString, ::Type{T})::Tuple{T, String} wh end function _get_json( - json::Dict, key::AbstractString, ::Type{T}; msg::Union{AbstractString, Nothing}=nothing + json::AbstractDict, key::AbstractString, ::Type{T}; msg::Union{AbstractString, Nothing}=nothing )::T where {T} value = get(json, key) do errormsg = """ @@ -186,7 +186,7 @@ function _get_json( end function _get_json_or( - json::Dict, + json::AbstractDict, key::AbstractString, ::Type{T}, default::U=nothing; @@ -199,7 +199,7 @@ end # parsing etc. The general signature is the following: # # function _get_json_convert( -# json::Dict, key::AbstractString, ::Type{T}; +# json::AbstractDict, key::AbstractString, ::Type{T}; # msg::Union{AbstractString, Nothing}=nothing # )::T # @@ -209,7 +209,7 @@ end # A key point, though, is that it will throw a JuliaHubError if the server response is somehow # invalid and we can't parse/convert it properly. function _get_json_convert( - json::Dict, key::AbstractString, ::Type{UUIDs.UUID}; msg::Union{AbstractString, Nothing}=nothing + json::AbstractDict, key::AbstractString, ::Type{UUIDs.UUID}; msg::Union{AbstractString, Nothing}=nothing )::UUIDs.UUID uuid_str = _get_json(json, key, String; msg) uuid = tryparse(UUIDs.UUID, uuid_str) @@ -355,7 +355,7 @@ function _walkfiles(f::Base.Callable, root::AbstractString; descend::Base.Callab end end -function _json_get(d::Dict, key, ::Type{T}; var::AbstractString, parse=false) where {T} +function _json_get(d::AbstractDict, key, ::Type{T}; var::AbstractString, parse=false) where {T} haskey(d, key) || _throw_jsonerror(var, "key `$key` missing", d) if parse isa(d[key], AbstractString) || _throw_jsonerror( @@ -372,7 +372,7 @@ function _json_get(d::Dict, key, ::Type{T}; var::AbstractString, parse=false) wh end end -function _throw_jsonerror(var::AbstractString, msg::AbstractString, json::Dict) +function _throw_jsonerror(var::AbstractString, msg::AbstractString, json::AbstractDict) e = JuliaHubError( """ Invalid JSON response from JuliaHub ($var): $msg @@ -383,7 +383,7 @@ function _throw_jsonerror(var::AbstractString, msg::AbstractString, json::Dict) end # Checks that the 'success' field is set and === true -function _json_check_success(json::Dict; var::AbstractString) +function _json_check_success(json::AbstractDict; var::AbstractString) success = _json_get(json, "success", Bool; var) success || throw(JuliaHubError( """ diff --git a/test/datasets.jl b/test/datasets.jl index f382ef23f..beca58b42 100644 --- a/test/datasets.jl +++ b/test/datasets.jl @@ -76,7 +76,7 @@ end # And correctly throw for invalid owner.username let d = Dict(d0()..., "owner" => nothing) @test_throws JuliaHub.JuliaHubError( - "Invalid JSON returned by the server: `owner` of type `Nothing`, expected `<: Dict`." + "Invalid JSON returned by the server: `owner` of type `Nothing`, expected `<: AbstractDict`." ) JuliaHub.Dataset(d) end diff --git a/test/mocking.jl b/test/mocking.jl index 794a33cb0..ea39d5537 100644 --- a/test/mocking.jl +++ b/test/mocking.jl @@ -534,7 +534,7 @@ Base.@kwdef mutable struct LogEngine max_response_size::Int = 10 end -function serve_kafka(logengine::LogEngine, method::Symbol, query::Dict) +function serve_kafka(logengine::LogEngine, method::Symbol, query::AbstractDict) jobname = get(query, "jobname", nothing) job = get(logengine.jobs, jobname, nothing) # If the client is doing a HEAD request, then we immediately return the @@ -612,7 +612,7 @@ function serve_kafka(logengine::LogEngine, method::Symbol, query::Dict) return JuliaHub._RESTResponse(200, logs_json) end -function serve_legacy(logengine::LogEngine, query::Dict) +function serve_legacy(logengine::LogEngine, query::AbstractDict) jobname = get(query, "jobname", nothing) job = get(logengine.jobs, jobname, nothing) if isnothing(jobname) From 7d254943444f3bc3ab3d101a6e6d046914b6b2dd Mon Sep 17 00:00:00 2001 From: Morten Piibeleht Date: Mon, 1 Dec 2025 12:18:57 +0200 Subject: [PATCH 2/4] fix --- src/jobsubmission.jl | 4 +++- src/utils.jl | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/jobsubmission.jl b/src/jobsubmission.jl index 03ce9805d..a7f60563d 100644 --- a/src/jobsubmission.jl +++ b/src/jobsubmission.jl @@ -914,7 +914,9 @@ struct PackageJob <: AbstractJobConfig args::Dict sysimage::Bool - PackageJob(app::PackageApp; args::AbstractDict=Dict(), sysimage::Bool=_DEFAULT_BatchJob_sysimage) = + PackageJob( + app::PackageApp; args::AbstractDict=Dict(), sysimage::Bool=_DEFAULT_BatchJob_sysimage + ) = new(app, app.name, app._registry.name, string(app._uuid), args, sysimage) PackageJob(app::UserApp; args::AbstractDict=Dict(), sysimage::Bool=_DEFAULT_BatchJob_sysimage) = new(app, app.name, nothing, app._repository, args, sysimage) diff --git a/src/utils.jl b/src/utils.jl index 21f017427..d327fe7ab 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -209,7 +209,8 @@ end # A key point, though, is that it will throw a JuliaHubError if the server response is somehow # invalid and we can't parse/convert it properly. function _get_json_convert( - json::AbstractDict, key::AbstractString, ::Type{UUIDs.UUID}; msg::Union{AbstractString, Nothing}=nothing + json::AbstractDict, key::AbstractString, ::Type{UUIDs.UUID}; + msg::Union{AbstractString, Nothing}=nothing )::UUIDs.UUID uuid_str = _get_json(json, key, String; msg) uuid = tryparse(UUIDs.UUID, uuid_str) From f225a873e07c6b6a408e5259f91c9621b1b0bccc Mon Sep 17 00:00:00 2001 From: Morten Piibeleht Date: Mon, 1 Dec 2025 12:20:15 +0200 Subject: [PATCH 3/4] remove event name from workflow titles --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 03283baee..ca4838868 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -17,7 +17,7 @@ concurrency: jobs: test: - name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ github.event_name }} + name: Julia ${{ matrix.version }} - ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false From 295543ac447f51d4b488b2a202ea08395b924365 Mon Sep 17 00:00:00 2001 From: Morten Piibeleht Date: Mon, 1 Dec 2025 14:32:51 +0200 Subject: [PATCH 4/4] fix formatting, again --- src/utils.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.jl b/src/utils.jl index d327fe7ab..9510020e6 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -210,7 +210,7 @@ end # invalid and we can't parse/convert it properly. function _get_json_convert( json::AbstractDict, key::AbstractString, ::Type{UUIDs.UUID}; - msg::Union{AbstractString, Nothing}=nothing + msg::Union{AbstractString, Nothing}=nothing, )::UUIDs.UUID uuid_str = _get_json(json, key, String; msg) uuid = tryparse(UUIDs.UUID, uuid_str)