Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update metadata API #53

Merged
merged 10 commits into from
Oct 3, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "DataAPI"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
authors = ["quinnj <quinn.jacobd@gmail.com>"]
version = "1.11.0"
version = "1.12.0"

[compat]
julia = "1"
Expand Down
119 changes: 62 additions & 57 deletions src/DataAPI.jl
Original file line number Diff line number Diff line change
Expand Up @@ -298,72 +298,92 @@ 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 if `col`is not a column of `x`.
bkamins marked this conversation as resolved.
Show resolved Hide resolved
"""

"""
metadata(x, key::AbstractString; style::Bool=false)
metadatasupport(T::Type)
nalimilan marked this conversation as resolved.
Show resolved Hide resolved

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}}` giving information if
values of type `T` support metadata. The `read` field indicates if
reading meteadata with the `metadata` and `metadatakeys` functions is supported.
The `write` field indicates if modifying metadata with the `metadata!`,
`deletemetadata!`, and `emptymetadata!` functions is supported.
bkamins marked this conversation as resolved.
Show resolved Hide resolved
"""
metadatasupport(::Type) = (read=false, write=false)

"""
colmetadatasupport(T::Type)

bkamins marked this conversation as resolved.
Show resolved Hide resolved
Return a `NamedTuple{(:read, :write), Tuple{Bool, Bool}}` giving information if
values of type `T` support column metadata. The `read` field indicates if
reading column meteadata with the `colmetadata` and `colmetadatakeys` functions
is supported. The `write` field indicates if modifying metadata with the
`colmetadata!`, `deletecolmetadata!`, and `emptycolmetadata!` functions is
supported.
bkamins marked this conversation as resolved.
Show resolved Hide resolved
"""
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)
bkamins marked this conversation as resolved.
Show resolved Hide resolved

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

Expand All @@ -372,73 +392,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
65 changes: 44 additions & 21 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 ()
Expand Down Expand Up @@ -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")
Expand All @@ -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"
Expand Down