Skip to content

Commit 32ef840

Browse files
authored
add metadata (#48)
1 parent 5c927b6 commit 32ef840

File tree

3 files changed

+286
-1
lines changed

3 files changed

+286
-1
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "DataAPI"
22
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
33
authors = ["quinnj <[email protected]>"]
4-
version = "1.10.0"
4+
version = "1.11.0"
55

66
[compat]
77
julia = "1"

src/DataAPI.jl

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,4 +287,158 @@ using a `sink` function to materialize the table.
287287
"""
288288
function allcombinations end
289289

290+
const STYLE_INFO = """
291+
One of the uses of the metadata `style` is decision
292+
how the metadata should be propagated when `x` is transformed. This interface
293+
defines the `:default` style that indicates that metadata should not be propagated
294+
under any operations (it is only preserved when a copy of the source table is
295+
performed). All types supporting metadata allow at least this style.
296+
"""
297+
298+
const COL_INFO = """
299+
`col` must have a type that is supported by table `x` for column indexing.
300+
Following the Tables.jl contract `Symbol` and `Int` are always allowed.
301+
Passing `col` that is not a column of `x` throws an error.
302+
"""
303+
304+
"""
305+
metadata(x, key::AbstractString; style::Bool=false)
306+
307+
Return metadata value associated with object `x` for key `key`.
308+
If `x` does not support metadata throw `ArgumentError`.
309+
If `x` supports metadata, but does not have a mapping for `key` throw
310+
`KeyError`.
311+
312+
If `style=true` return a tuple of metadata value and metadata style. Metadata
313+
style is an additional information about the kind of metadata that is stored
314+
for the `key`.
315+
316+
$STYLE_INFO
317+
"""
318+
metadata(::T, ::AbstractString; style::Bool=false) where {T} =
319+
throw(ArgumentError("Objects of type $T do not support getting metadata"))
320+
321+
"""
322+
metadatakeys(x)
323+
324+
Return an iterator of metadata keys for which `metadata(x, key)` returns a
325+
metadata value. If `x` does not support metadata return `()`.
326+
"""
327+
metadatakeys(::Any) = ()
328+
329+
"""
330+
metadata!(x, key::AbstractString, value; style)
331+
332+
Set metadata for object `x` for key `key` to have value `value`
333+
and style `style` and return `x`.
334+
If `x` does not support setting metadata throw `ArgumentError`.
335+
336+
$STYLE_INFO
337+
"""
338+
metadata!(::T, ::AbstractString, ::Any; style) where {T} =
339+
throw(ArgumentError("Objects of type $T do not support setting metadata"))
340+
341+
"""
342+
deletemetadata!(x, key::AbstractString)
343+
344+
Delete metadata for object `x` for key `key` and return `x`
345+
(if metadata for `key` is not present do not perform any action).
346+
If `x` does not support metadata deletion throw `ArgumentError`.
347+
"""
348+
deletemetadata!(::T, ::AbstractString) where {T} =
349+
throw(ArgumentError("Objects of type $T do not support metadata deletion"))
350+
351+
"""
352+
emptymetadata!(x)
353+
354+
Delete all metadata for object `x`.
355+
If `x` does not support metadata deletion throw `ArgumentError`.
356+
"""
357+
emptymetadata!(::T) where {T} =
358+
throw(ArgumentError("Objects of type $T do not support metadata deletion"))
359+
360+
"""
361+
colmetadata(x, col, key::AbstractString; style::Bool=false)
362+
363+
Return metadata value associated with table `x` for column `col` and key `key`.
364+
If `x` does not support metadata for column `col` throw `ArgumentError`. If `x`
365+
supports metadata, but does not have a mapping for column `col` for `key` throw
366+
`KeyError`.
367+
368+
$COL_INFO
369+
370+
If `style=true` return a tuple of metadata value and metadata style. Metadata
371+
style is an additional information about the kind of metadata that is stored for
372+
the `key`.
373+
374+
$STYLE_INFO
375+
"""
376+
colmetadata(::T, ::Int, ::AbstractString; style::Bool=false) where {T} =
377+
throw(ArgumentError("Objects of type $T do not support getting column metadata"))
378+
colmetadata(::T, ::Symbol, ::AbstractString; style::Bool=false) where {T} =
379+
throw(ArgumentError("Objects of type $T do not support getting column metadata"))
380+
381+
"""
382+
colmetadatakeys(x, [col])
383+
384+
If `col` is passed return an iterator of metadata keys for which `metadata(x,
385+
col, key)` returns a metadata value. If `x` does not support metadata for column
386+
`col` return `()`.
387+
388+
`col` must have a type that is supported by table `x` for column indexing.
389+
Following the Tables.jl contract `Symbol` and `Int` are always allowed. Passing
390+
`col` that is not a column of `x` either throws an error (this is a
391+
preferred behavior if it is possible) or returns `()` (this duality is allowed
392+
as some Tables.jl tables do not have a schema).
393+
394+
If `col` is not passed return an iterator of `col => colmetadatakeys(x, col)`
395+
pairs for all columns that have metadata, where `col` are `Symbol`.
396+
If `x` does not support column metadata return `()`.
397+
"""
398+
colmetadatakeys(::Any, ::Int) = ()
399+
colmetadatakeys(::Any, ::Symbol) = ()
400+
colmetadatakeys(::Any) = ()
401+
402+
"""
403+
colmetadata!(x, col, key::AbstractString, value; style)
404+
405+
Set metadata for table `x` for column `col` for key `key` to have value `value`
406+
and style `style` and return `x`.
407+
If `x` does not support setting metadata for column `col` throw `ArgumentError`.
408+
409+
$COL_INFO
410+
411+
$STYLE_INFO
412+
"""
413+
colmetadata!(::T, ::Int, ::AbstractString, ::Any; style) where {T} =
414+
throw(ArgumentError("Objects of type $T do not support setting metadata"))
415+
colmetadata!(::T, ::Symbol, ::AbstractString, ::Any; style) where {T} =
416+
throw(ArgumentError("Objects of type $T do not support setting metadata"))
417+
418+
"""
419+
deletecolmetadata!(x, col, key::AbstractString)
420+
421+
Delete metadata for table `x` for column `col` for key `key` and return `x`
422+
(if metadata for `key` is not present do not perform any action).
423+
If `x` does not support metadata deletion for column `col` throw `ArgumentError`.
424+
"""
425+
deletecolmetadata!(::T, ::Symbol, ::AbstractString) where {T} =
426+
throw(ArgumentError("Objects of type $T do not support metadata deletion"))
427+
deletecolmetadata!(::T, ::Int, ::AbstractString) where {T} =
428+
throw(ArgumentError("Objects of type $T do not support metadata deletion"))
429+
430+
"""
431+
emptycolmetadata!(x, [col])
432+
433+
Delete all metadata for table `x` for column `col`.
434+
If `col` is not passed delete all column level metadata for table `x`.
435+
If `x` does not support metadata deletion for column `col` throw `ArgumentError`.
436+
"""
437+
emptycolmetadata!(::T, ::Symbol) where {T} =
438+
throw(ArgumentError("Objects of type $T do not support metadata deletion"))
439+
emptycolmetadata!(::T, ::Int) where {T} =
440+
throw(ArgumentError("Objects of type $T do not support metadata deletion"))
441+
emptycolmetadata!(::T) where {T} =
442+
throw(ArgumentError("Objects of type $T do not support metadata deletion"))
443+
290444
end # module

test/runtests.jl

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,79 @@ Base.size(x::TestArray) = size(x.x)
1010
Base.getindex(x::TestArray, i) = x.x[i]
1111
DataAPI.levels(x::TestArray) = reverse(DataAPI.levels(x.x))
1212

13+
# An example implementation of metadata
14+
# For simplicity Int col indexing is not implemented
15+
# and no checking if col is a column of a table is performed
16+
17+
struct TestMeta
18+
table::Dict{String, Any}
19+
col::Dict{Symbol, Dict{String, Any}}
20+
21+
TestMeta() = new(Dict{String, Any}(), Dict{Symbol, Dict{String, Any}}())
22+
end
23+
24+
function DataAPI.metadata(x::TestMeta, key::AbstractString; style::Bool=false)
25+
return style ? x.table[key] : x.table[key][1]
26+
end
27+
28+
DataAPI.metadatakeys(x::TestMeta) = keys(x.table)
29+
30+
function DataAPI.metadata!(x::TestMeta, key::AbstractString, value; style)
31+
x.table[key] = (value, style)
32+
return x
33+
end
34+
35+
function DataAPI.metadata!(x::TestMeta, key::AbstractString, value; style)
36+
x.table[key] = (value, style)
37+
return x
38+
end
39+
40+
DataAPI.deletemetadata!(x::TestMeta, key::AbstractString) = delete!(x.table, key)
41+
DataAPI.emptymetadata!(x::TestMeta) = empty!(x.table)
42+
43+
function DataAPI.colmetadata(x::TestMeta, col::Symbol, key::AbstractString; style::Bool=false)
44+
return style ? x.col[col][key] : x.col[col][key][1]
45+
end
46+
47+
function DataAPI.colmetadatakeys(x::TestMeta, col::Symbol)
48+
haskey(x.col, col) && return keys(x.col[col])
49+
return ()
50+
end
51+
52+
function DataAPI.colmetadatakeys(x::TestMeta)
53+
isempty(x.col) && return ()
54+
return (col => keys(x.col[col]) for col in keys(x.col))
55+
end
56+
57+
function DataAPI.colmetadata!(x::TestMeta, col::Symbol, key::AbstractString, value; style)
58+
if haskey(x.col, col)
59+
x.col[col][key] = (value, style)
60+
else
61+
x.col[col] = Dict{Any, Any}(key => (value, style))
62+
end
63+
return x
64+
end
65+
66+
function DataAPI.deletecolmetadata!(x::TestMeta, col::Symbol, key::AbstractString)
67+
if haskey(x.col, col)
68+
delete!(x.col[col], key)
69+
else
70+
throw(ArgumentError("column $col not found"))
71+
end
72+
return x
73+
end
74+
75+
function DataAPI.emptycolmetadata!(x::TestMeta, col::Symbol)
76+
if haskey(x.col, col)
77+
delete!(x.col, col)
78+
else
79+
throw(ArgumentError("column $col not found"))
80+
end
81+
return x
82+
end
83+
84+
DataAPI.emptycolmetadata!(x::TestMeta) = empty!(x.col)
85+
1386
@testset "DataAPI" begin
1487

1588
@testset "defaultarray" begin
@@ -173,4 +246,62 @@ end
173246
@test DataAPI.unwrap(missing) === missing
174247
end
175248

249+
@testset "metadata" begin
250+
@test_throws ArgumentError DataAPI.metadata!(1, "a", 10, style=:default)
251+
@test_throws ArgumentError DataAPI.deletemetadata!(1, "a")
252+
@test_throws ArgumentError DataAPI.emptymetadata!(1)
253+
@test_throws ArgumentError DataAPI.metadata(1, "a")
254+
@test_throws ArgumentError DataAPI.metadata(1, "a", style=true)
255+
@test DataAPI.metadatakeys(1) == ()
256+
257+
@test_throws ArgumentError DataAPI.colmetadata!(1, :col, "a", 10, style=:default)
258+
@test_throws ArgumentError DataAPI.deletecolmetadata!(1, :col, "a")
259+
@test_throws ArgumentError DataAPI.emptycolmetadata!(1, :col)
260+
@test_throws ArgumentError DataAPI.deletecolmetadata!(1, 1, "a")
261+
@test_throws ArgumentError DataAPI.emptycolmetadata!(1, 1)
262+
@test_throws ArgumentError DataAPI.emptycolmetadata!(1)
263+
@test_throws ArgumentError DataAPI.colmetadata(1, :col, "a")
264+
@test_throws ArgumentError DataAPI.colmetadata(1, :col, "a", style=true)
265+
@test_throws ArgumentError DataAPI.colmetadata!(1, 1, "a", 10, style=:default)
266+
@test_throws ArgumentError DataAPI.colmetadata(1, 1, "a")
267+
@test_throws ArgumentError DataAPI.colmetadata(1, 1, "a", style=true)
268+
@test DataAPI.colmetadatakeys(1, :col) == ()
269+
@test DataAPI.colmetadatakeys(1, 1) == ()
270+
@test DataAPI.colmetadatakeys(1) == ()
271+
272+
tm = TestMeta()
273+
@test isempty(DataAPI.metadatakeys(tm))
274+
@test DataAPI.metadata!(tm, "a", "100", style=:note) == tm
275+
@test collect(DataAPI.metadatakeys(tm)) == ["a"]
276+
@test_throws KeyError DataAPI.metadata(tm, "b")
277+
@test_throws KeyError DataAPI.metadata(tm, "b", style=true)
278+
@test DataAPI.metadata(tm, "a") == "100"
279+
@test DataAPI.metadata(tm, "a", style=true) == ("100", :note)
280+
DataAPI.deletemetadata!(tm, "a")
281+
@test isempty(DataAPI.metadatakeys(tm))
282+
@test DataAPI.metadata!(tm, "a", "100", style=:note) == tm
283+
DataAPI.emptymetadata!(tm)
284+
@test isempty(DataAPI.metadatakeys(tm))
285+
286+
@test DataAPI.colmetadatakeys(tm) == ()
287+
@test DataAPI.colmetadatakeys(tm, :col) == ()
288+
@test DataAPI.colmetadata!(tm, :col, "a", "100", style=:note) == tm
289+
@test [k => collect(v) for (k, v) in DataAPI.colmetadatakeys(tm)] == [:col => ["a"]]
290+
@test collect(DataAPI.colmetadatakeys(tm, :col)) == ["a"]
291+
@test_throws KeyError DataAPI.colmetadata(tm, :col, "b")
292+
@test_throws KeyError DataAPI.colmetadata(tm, :col, "b", style=true)
293+
@test_throws KeyError DataAPI.colmetadata(tm, :col2, "a")
294+
@test_throws KeyError DataAPI.colmetadata(tm, :col2, "a", style=true)
295+
@test DataAPI.colmetadata(tm, :col, "a") == "100"
296+
@test DataAPI.colmetadata(tm, :col, "a", style=true) == ("100", :note)
297+
DataAPI.deletecolmetadata!(tm, :col, "a")
298+
@test isempty(DataAPI.colmetadatakeys(tm, :col))
299+
@test DataAPI.colmetadata!(tm, :col, "a", "100", style=:note) == tm
300+
DataAPI.emptycolmetadata!(tm, :col)
301+
@test isempty(DataAPI.colmetadatakeys(tm, :col))
302+
@test DataAPI.colmetadata!(tm, :col, "a", "100", style=:note) == tm
303+
DataAPI.emptycolmetadata!(tm)
304+
@test isempty(DataAPI.colmetadatakeys(tm))
305+
end
306+
176307
end # @testset "DataAPI"

0 commit comments

Comments
 (0)