diff --git a/Project.toml b/Project.toml index 5106aa0..9b720f8 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "DataAPI" uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" authors = ["quinnj "] -version = "1.11.0" +version = "1.12.0" [compat] julia = "1" diff --git a/src/DataAPI.jl b/src/DataAPI.jl index 9cc1dfc..33076fa 100644 --- a/src/DataAPI.jl +++ b/src/DataAPI.jl @@ -298,72 +298,97 @@ performed). All types supporting metadata allow at least this style. const COL_INFO = """ `col` must have a type that is supported by table `x` for column indexing. Following the Tables.jl contract `Symbol` and `Int` are always allowed. -Passing `col` that is not a column of `x` throws an error. +Throw an error if `col` is not a column of `x`. """ """ - metadata(x, key::AbstractString; style::Bool=false) + metadatasupport(T::Type) -Return metadata value associated with object `x` for key `key`. -If `x` does not support metadata throw `ArgumentError`. -If `x` supports metadata, but does not have a mapping for `key` throw -`KeyError`. +Return a `NamedTuple{(:read, :write), Tuple{Bool, Bool}}` indicating whether +values of type `T` support metadata. + +The `read` field indicates whether reading metadata with the [`metadata`](@ref) +and [`metadatakeys`]](@ref) functions is supported. + +The `write` field indicates whether modifying metadata with the [`metadata!`](@ref), +[`deletemetadata!`](@ref), and [`emptymetadata!`](@ref) functions is supported. +""" +metadatasupport(::Type) = (read=false, write=false) + +""" + colmetadatasupport(T::Type) + +Return a `NamedTuple{(:read, :write), Tuple{Bool, Bool}}` indicating whether +values of type `T` support column metadata. + +The `read` field indicates whether reading metadata with the [`colmetadata`](@ref) +and [`colmetadatakeys`](@ref) functions is supported. + +The `write` field indicates whether modifying metadata with the [`colmetadata!`](@ref), +[`deletecolmetadata!`](@ref), and [`emptycolmetadata!`](@ref) functions is supported. +""" +colmetadatasupport(::Type) = (read=false, write=false) + +""" + metadata(x, key::AbstractString, [default]; style::Bool=false) + +Return metadata value associated with object `x` for key `key`. Throw an error +if `x` does not support reading metadata or does not have a mapping for `key`. If `style=true` return a tuple of metadata value and metadata style. Metadata -style is an additional information about the kind of metadata that is stored -for the `key`. +style is an additional information about the kind of metadata that is stored for +the `key`. $STYLE_INFO + +If `default` is passed then return it if reading metadata is supported but +mapping for `key` is missing. If `style=true` return `(default, :default)`. """ -metadata(::T, ::AbstractString; style::Bool=false) where {T} = - throw(ArgumentError("Objects of type $T do not support getting metadata")) +function metadata end """ metadatakeys(x) Return an iterator of metadata keys for which `metadata(x, key)` returns a -metadata value. If `x` does not support metadata return `()`. +metadata value. +Throw an error if `x` does not support reading metadata. """ -metadatakeys(::Any) = () +function metadatakeys end """ metadata!(x, key::AbstractString, value; style) Set metadata for object `x` for key `key` to have value `value` and style `style` and return `x`. -If `x` does not support setting metadata throw `ArgumentError`. +Throw an error if `x` does not support setting metadata. $STYLE_INFO """ -metadata!(::T, ::AbstractString, ::Any; style) where {T} = - throw(ArgumentError("Objects of type $T do not support setting metadata")) +function metadata! end """ deletemetadata!(x, key::AbstractString) Delete metadata for object `x` for key `key` and return `x` (if metadata for `key` is not present do not perform any action). -If `x` does not support metadata deletion throw `ArgumentError`. +Throw an error if `x` does not support metadata deletion. """ -deletemetadata!(::T, ::AbstractString) where {T} = - throw(ArgumentError("Objects of type $T do not support metadata deletion")) +function deletemetadata! end """ emptymetadata!(x) Delete all metadata for object `x`. -If `x` does not support metadata deletion throw `ArgumentError`. +Throw an error if `x` does not support metadata deletion. """ -emptymetadata!(::T) where {T} = - throw(ArgumentError("Objects of type $T do not support metadata deletion")) +function emptymetadata! end """ - colmetadata(x, col, key::AbstractString; style::Bool=false) + colmetadata(x, col, key::AbstractString, [default]; style::Bool=false) Return metadata value associated with table `x` for column `col` and key `key`. -If `x` does not support metadata for column `col` throw `ArgumentError`. If `x` -supports metadata, but does not have a mapping for column `col` for `key` throw -`KeyError`. +Throw an error if `x` does not support reading metadata for column `col` or `x` +supports reading metadata, but does not have a mapping for column `col` for `key`. $COL_INFO @@ -372,73 +397,58 @@ style is an additional information about the kind of metadata that is stored for the `key`. $STYLE_INFO + +If `default` is passed then return it if `x` supports reading metadata and has +column `col` but mapping for `key` is missing. +If `style=true` return `(default, :default)`. """ -colmetadata(::T, ::Int, ::AbstractString; style::Bool=false) where {T} = - throw(ArgumentError("Objects of type $T do not support getting column metadata")) -colmetadata(::T, ::Symbol, ::AbstractString; style::Bool=false) where {T} = - throw(ArgumentError("Objects of type $T do not support getting column metadata")) +function colmetadata end """ colmetadatakeys(x, [col]) -If `col` is passed return an iterator of metadata keys for which `metadata(x, -col, key)` returns a metadata value. If `x` does not support metadata for column -`col` return `()`. +If `col` is passed return an iterator of metadata keys for which +`metadata(x, col, key)` returns a metadata value. Throw an error if `x` does not +support reading column metadata or if `col` is not a column of `x`. `col` must have a type that is supported by table `x` for column indexing. -Following the Tables.jl contract `Symbol` and `Int` are always allowed. Passing -`col` that is not a column of `x` either throws an error (this is a -preferred behavior if it is possible) or returns `()` (this duality is allowed -as some Tables.jl tables do not have a schema). +Following the Tables.jl contract `Symbol` and `Int` are always allowed. If `col` is not passed return an iterator of `col => colmetadatakeys(x, col)` pairs for all columns that have metadata, where `col` are `Symbol`. If `x` does not support column metadata return `()`. """ -colmetadatakeys(::Any, ::Int) = () -colmetadatakeys(::Any, ::Symbol) = () -colmetadatakeys(::Any) = () +function colmetadatakeys end """ colmetadata!(x, col, key::AbstractString, value; style) Set metadata for table `x` for column `col` for key `key` to have value `value` and style `style` and return `x`. -If `x` does not support setting metadata for column `col` throw `ArgumentError`. +Throw an error if `x` does not support setting metadata for column `col`. $COL_INFO $STYLE_INFO """ -colmetadata!(::T, ::Int, ::AbstractString, ::Any; style) where {T} = - throw(ArgumentError("Objects of type $T do not support setting metadata")) -colmetadata!(::T, ::Symbol, ::AbstractString, ::Any; style) where {T} = - throw(ArgumentError("Objects of type $T do not support setting metadata")) +function colmetadata! end """ deletecolmetadata!(x, col, key::AbstractString) Delete metadata for table `x` for column `col` for key `key` and return `x` (if metadata for `key` is not present do not perform any action). -If `x` does not support metadata deletion for column `col` throw `ArgumentError`. +Throw an error if `x` does not support metadata deletion for column `col`. """ -deletecolmetadata!(::T, ::Symbol, ::AbstractString) where {T} = - throw(ArgumentError("Objects of type $T do not support metadata deletion")) -deletecolmetadata!(::T, ::Int, ::AbstractString) where {T} = - throw(ArgumentError("Objects of type $T do not support metadata deletion")) +function deletecolmetadata! end """ emptycolmetadata!(x, [col]) Delete all metadata for table `x` for column `col`. If `col` is not passed delete all column level metadata for table `x`. -If `x` does not support metadata deletion for column `col` throw `ArgumentError`. -""" -emptycolmetadata!(::T, ::Symbol) where {T} = - throw(ArgumentError("Objects of type $T do not support metadata deletion")) -emptycolmetadata!(::T, ::Int) where {T} = - throw(ArgumentError("Objects of type $T do not support metadata deletion")) -emptycolmetadata!(::T) where {T} = - throw(ArgumentError("Objects of type $T do not support metadata deletion")) +Throw an error if `x` does not support metadata deletion for column `col`. +""" +function emptycolmetadata! end end # module diff --git a/test/runtests.jl b/test/runtests.jl index c8384de..5b4fc3b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -21,10 +21,18 @@ struct TestMeta TestMeta() = new(Dict{String, Any}(), Dict{Symbol, Dict{String, Any}}()) end +DataAPI.metadatasupport(::Type{TestMeta}) = (read=true, write=true) +DataAPI.colmetadatasupport(::Type{TestMeta}) = (read=true, write=true) + function DataAPI.metadata(x::TestMeta, key::AbstractString; style::Bool=false) return style ? x.table[key] : x.table[key][1] end +function DataAPI.metadata(x::TestMeta, key::AbstractString, default; style::Bool=false) + haskey(x.table, key) && return DataAPI.metadata(x, key, style=style) + return style ? (default, :default) : default +end + DataAPI.metadatakeys(x::TestMeta) = keys(x.table) function DataAPI.metadata!(x::TestMeta, key::AbstractString, value; style) @@ -44,6 +52,11 @@ function DataAPI.colmetadata(x::TestMeta, col::Symbol, key::AbstractString; styl return style ? x.col[col][key] : x.col[col][key][1] end +function DataAPI.colmetadata(x::TestMeta, col::Symbol, key::AbstractString, default; style::Bool=false) + haskey(x.table, col) && haskey(x.table[col], key) && return DataAPI.metadata(x, key, style=style) + return style ? (default, :default) : default +end + function DataAPI.colmetadatakeys(x::TestMeta, col::Symbol) haskey(x.col, col) && return keys(x.col[col]) return () @@ -247,34 +260,42 @@ end end @testset "metadata" begin - @test_throws ArgumentError DataAPI.metadata!(1, "a", 10, style=:default) - @test_throws ArgumentError DataAPI.deletemetadata!(1, "a") - @test_throws ArgumentError DataAPI.emptymetadata!(1) - @test_throws ArgumentError DataAPI.metadata(1, "a") - @test_throws ArgumentError DataAPI.metadata(1, "a", style=true) - @test DataAPI.metadatakeys(1) == () - - @test_throws ArgumentError DataAPI.colmetadata!(1, :col, "a", 10, style=:default) - @test_throws ArgumentError DataAPI.deletecolmetadata!(1, :col, "a") - @test_throws ArgumentError DataAPI.emptycolmetadata!(1, :col) - @test_throws ArgumentError DataAPI.deletecolmetadata!(1, 1, "a") - @test_throws ArgumentError DataAPI.emptycolmetadata!(1, 1) - @test_throws ArgumentError DataAPI.emptycolmetadata!(1) - @test_throws ArgumentError DataAPI.colmetadata(1, :col, "a") - @test_throws ArgumentError DataAPI.colmetadata(1, :col, "a", style=true) - @test_throws ArgumentError DataAPI.colmetadata!(1, 1, "a", 10, style=:default) - @test_throws ArgumentError DataAPI.colmetadata(1, 1, "a") - @test_throws ArgumentError DataAPI.colmetadata(1, 1, "a", style=true) - @test DataAPI.colmetadatakeys(1, :col) == () - @test DataAPI.colmetadatakeys(1, 1) == () - @test DataAPI.colmetadatakeys(1) == () + @test_throws MethodError DataAPI.metadata!(1, "a", 10, style=:default) + @test_throws MethodError DataAPI.deletemetadata!(1, "a") + @test_throws MethodError DataAPI.emptymetadata!(1) + @test_throws MethodError DataAPI.metadata(1, "a") + @test_throws MethodError DataAPI.metadata(1, "a", style=true) + @test_throws MethodError DataAPI.metadatakeys(1) + + @test_throws MethodError DataAPI.colmetadata!(1, :col, "a", 10, style=:default) + @test_throws MethodError DataAPI.deletecolmetadata!(1, :col, "a") + @test_throws MethodError DataAPI.emptycolmetadata!(1, :col) + @test_throws MethodError DataAPI.deletecolmetadata!(1, 1, "a") + @test_throws MethodError DataAPI.emptycolmetadata!(1, 1) + @test_throws MethodError DataAPI.emptycolmetadata!(1) + @test_throws MethodError DataAPI.colmetadata(1, :col, "a") + @test_throws MethodError DataAPI.colmetadata(1, :col, "a", style=true) + @test_throws MethodError DataAPI.colmetadata!(1, 1, "a", 10, style=:default) + @test_throws MethodError DataAPI.colmetadata(1, 1, "a") + @test_throws MethodError DataAPI.colmetadata(1, 1, "a", style=true) + @test_throws MethodError DataAPI.colmetadatakeys(1, :col) + @test_throws MethodError DataAPI.colmetadatakeys(1, 1) + @test_throws MethodError DataAPI.colmetadatakeys(1) + + @test DataAPI.metadatasupport(Int) == (read=false, write=false) + @test DataAPI.colmetadatasupport(Int) == (read=false, write=false) tm = TestMeta() + @test DataAPI.metadatasupport(TestMeta) == (read=true, write=true) + @test DataAPI.colmetadatasupport(TestMeta) == (read=true, write=true) + @test isempty(DataAPI.metadatakeys(tm)) @test DataAPI.metadata!(tm, "a", "100", style=:note) == tm @test collect(DataAPI.metadatakeys(tm)) == ["a"] @test_throws KeyError DataAPI.metadata(tm, "b") + @test DataAPI.metadata(tm, "b", 123) == 123 @test_throws KeyError DataAPI.metadata(tm, "b", style=true) + @test DataAPI.metadata(tm, "b", 123, style=true) == (123, :default) @test DataAPI.metadata(tm, "a") == "100" @test DataAPI.metadata(tm, "a", style=true) == ("100", :note) DataAPI.deletemetadata!(tm, "a") @@ -289,7 +310,9 @@ end @test [k => collect(v) for (k, v) in DataAPI.colmetadatakeys(tm)] == [:col => ["a"]] @test collect(DataAPI.colmetadatakeys(tm, :col)) == ["a"] @test_throws KeyError DataAPI.colmetadata(tm, :col, "b") + @test DataAPI.colmetadata(tm, :col, "b", 123) == 123 @test_throws KeyError DataAPI.colmetadata(tm, :col, "b", style=true) + @test DataAPI.colmetadata(tm, :col, "b", 123, style=true) == (123, :default) @test_throws KeyError DataAPI.colmetadata(tm, :col2, "a") @test_throws KeyError DataAPI.colmetadata(tm, :col2, "a", style=true) @test DataAPI.colmetadata(tm, :col, "a") == "100"