Skip to content

Commit bf1f315

Browse files
committed
feat: add metadata manipulation functions and DimensionalData extension
1 parent 446e1be commit bf1f315

File tree

9 files changed

+125
-6
lines changed

9 files changed

+125
-6
lines changed

Project.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@ version = "0.1.12"
77
Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697"
88
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
99

10+
[weakdeps]
11+
DimensionalData = "0703355e-b756-11e9-17c0-8b28908087d0"
12+
13+
[extensions]
14+
SpaceDataModelDimensionalDataExt = "DimensionalData"
15+
1016
[compat]
1117
Accessors = "0.1"
1218
Dates = "1"
19+
DimensionalData = "0.29"
1320
julia = "1.10"

docs/src/interface.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,22 @@ SpaceDataModel.jl provides a set of abstractions and generic functions that can
66

77
- [DataAPI.jl](https://github.com/JuliaData/DataAPI.jl): A data-focused namespace for packages to share functions
88

9+
## Metadata
10+
11+
The package exports the following functions for metadata handling:
12+
13+
- [`getmeta`](@ref): Get metadata for an object
14+
- [`setmeta`](@ref): Update metadata for an object
15+
- [`setmeta!`](@ref): Update metadata for an object in-place
16+
17+
These functions are generic and can be extended to support custom data types.
18+
19+
```@docs; canonical=false
20+
getmeta
21+
setmeta
22+
setmeta!
23+
```
24+
925
## Coordinate Systems
1026

1127
The package exports three key components for coordinate system handling:
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module SpaceDataModelDimensionalDataExt
2+
using DimensionalData: AbstractDimArray, NoMetadata, metadata
3+
import SpaceDataModel: meta, _merge
4+
5+
_merge(::NoMetadata, d, rest...) = merge(d, rest...)
6+
meta(A::AbstractDimArray) = metadata(A)
7+
end

src/SpaceDataModel.jl

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export AbstractDataVariable
1010
export Project, Instrument, DataSet, LDataSet, Product
1111
export Event
1212
export AbstractCoordinateSystem, AbstractCoordinateVector, getcsys
13+
export getmeta, setmeta!, setmeta
1314

1415
include("utils.jl")
1516
include("metadata.jl")
@@ -25,8 +26,25 @@ include("coord.jl")
2526
include("workload.jl")
2627

2728
# Interface
28-
name(v) = @getfield v :name get(meta(v), "name", "")
29-
meta(v) = @getfield v (:meta, :metadata) NoMetadata()
29+
name(v) = @getfield v :name getmeta(v, "name", "")
30+
31+
"""
32+
getmeta(x)
33+
34+
Get metadata for object `x`. If `x` does not have metadata, return `NoMetadata()`.
35+
36+
"""
37+
getmeta(x) = @getfield x (:meta, :metadata) NoMetadata()
38+
39+
"""
40+
getmeta(x, key, default=nothing)
41+
42+
Get metadata value associated with object `x` for key `key`, or `default` if `key` is not present.
43+
"""
44+
getmeta(x, key, default=nothing) = get(meta(x), key, default)
45+
46+
meta(x) = getmeta(x) # not exported (to be removed)
47+
3048
units(v) = @get(v, "units", nothing)
3149
times(v) = @getfield v (:times, :time)
3250

@@ -35,4 +53,43 @@ function unit(v)
3553
allequal(us) ? only(us) : error("Units are not equal: $us")
3654
end
3755

56+
"""
57+
setmeta!(x, key => value, ...; symbolkey => value2, ...)
58+
setmeta!(x, dict::AbstractDict)
59+
60+
Update metadata for object `x` in-place and return `x`. The metadata container must be mutable.
61+
62+
The arguments could be multiple key-value pairs or a dictionary of metadata; keyword arguments are also accepted.
63+
64+
# Examples
65+
```julia
66+
setmeta!(x, :units => "m/s", :source => "sensor")
67+
setmeta!(x, Dict(:units => "m/s", :quality => "good"))
68+
setmeta!(x; units="m/s", calibrated=true)
69+
```
70+
71+
Throws an error if the metadata is not mutable. Use `setmeta` for immutable metadata.
72+
"""
73+
function setmeta! end
74+
75+
function setmeta!(x, args...; kw...)
76+
m = meta(x)
77+
ismutable(m) || error("Metadata is not mutable, use `setmeta` instead")
78+
set!(m, args...; kw...)
79+
return x
80+
end
81+
82+
"""
83+
setmeta(x, key => value, ...; symbolkey => value2, ...)
84+
setmeta(x, dict::AbstractDict)
85+
86+
Update metadata for object `x` for key `key` to have value `value` and return `x`.
87+
"""
88+
function setmeta end
89+
90+
function setmeta(x, args::Pair...; kw...)
91+
return @set x.metadata=_merge(meta(x), Dict(args...), kw)
3892
end
93+
setmeta(x, dict::AbstractDict) = @set x.metadata=_merge(meta(x), dict)
94+
95+
end

src/metadata.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ Base.values(::NoMetadata) = ()
1717
Base.iterate(::NoMetadata) = nothing
1818

1919
# Allow merging NoMetadata with a Dict or keyword arguments
20-
Base.merge(::NoMetadata, d::AbstractDict) = length(d) == 0 ? NoMetadata() : copy(d)
21-
Base.merge(::NoMetadata, p::Base.Pairs) = length(p) == 0 ? NoMetadata() : p
22-
Base.merge(m::NoMetadata, d, rest...) = merge(merge(m, d), rest...)
20+
function Base.merge(::NoMetadata, d, rest...)
21+
res = merge(d, rest...)
22+
isempty(res) ? NoMetadata() : res # for cases where no kwarg is provided
23+
end
2324

2425
Base.haskey(::NoMetadata, args...) = false
2526
Base.get(::NoMetadata, key, default=nothing) = default

src/utils.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,14 @@ function colors(i)
8686
end
8787

8888
print_name(io::IO, var) = printstyled(io, name(var); color=colors(7))
89+
90+
# like merge to avoid privacy issues
91+
_merge(a, b...) = merge(a, b...)
92+
93+
function set!(d::AbstractDict, args::Pair...; kw...)
94+
for (k, v) in args
95+
d[k] = v
96+
end
97+
return merge!(d, kw)
98+
end
99+
set!(d::AbstractDict, dict::AbstractDict; kw...) = merge!(d, dict, kw)

test/DimensionalData.jl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
@testitem "DimensionalData Extension Tests" begin
2+
using DimensionalData
3+
# Create test data
4+
A = DimArray(rand(10, 5), (X(1:10), Y(1:5)))
5+
A_mutable = rebuild(A, metadata=Dict())
6+
7+
# Test with key-value pairs and keyword arguments
8+
@test_nowarn setmeta(A, "string" => "supported", description="velocity", calibrated=true)
9+
@test_throws ErrorException setmeta!(A, "string" => "supported", calibrated=true)
10+
@test_nowarn setmeta!(A_mutable, "string" => "supported", calibrated=true)
11+
@test A_mutable.metadata == Dict("string" => "supported", :calibrated => true)
12+
13+
# Test with Dict
14+
meta_dict = Dict("string" => "supported", :calibrated => false)
15+
@test_nowarn setmeta(A, meta_dict)
16+
@test_nowarn setmeta(A_mutable, meta_dict)
17+
@test_nowarn setmeta!(A_mutable, meta_dict)
18+
@test A_mutable.metadata == meta_dict
19+
end

test/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[deps]
22
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
33
CheckConcreteStructs = "73c92db5-9da6-4938-911a-6443a7e94a58"
4+
DimensionalData = "0703355e-b756-11e9-17c0-8b28908087d0"
45
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
56
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
67
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"

test/product.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
# Test construction with kwargs that become metadata
2525
p5 = Product([1, 2, 3]; units="m/s", description="velocity")
26-
@test p5.metadata isa Base.Pairs
26+
@test p5.metadata isa AbstractDict
2727
@test p5.metadata[:units] == "m/s"
2828
@test p5.metadata[:description] == "velocity"
2929

0 commit comments

Comments
 (0)