Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ version = "0.1.12"
Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"

[weakdeps]
DimensionalData = "0703355e-b756-11e9-17c0-8b28908087d0"

[extensions]
SpaceDataModelDimensionalDataExt = "DimensionalData"

[compat]
Accessors = "0.1"
Dates = "1"
DimensionalData = "0.29"
julia = "1.10"
16 changes: 16 additions & 0 deletions docs/src/interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,22 @@ SpaceDataModel.jl provides a set of abstractions and generic functions that can

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

## Metadata

The package exports the following functions for metadata handling:

- [`getmeta`](@ref): Get metadata for an object
- [`setmeta`](@ref): Update metadata for an object
- [`setmeta!`](@ref): Update metadata for an object in-place

These functions are generic and can be extended to support custom data types.

```@docs; canonical=false
getmeta
setmeta
setmeta!
```

## Coordinate Systems

The package exports three key components for coordinate system handling:
Expand Down
7 changes: 7 additions & 0 deletions ext/SpaceDataModelDimensionalDataExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module SpaceDataModelDimensionalDataExt
using DimensionalData: AbstractDimArray, NoMetadata, metadata
import SpaceDataModel: meta, _merge

_merge(::NoMetadata, d, rest...) = merge(d, rest...)
meta(A::AbstractDimArray) = metadata(A)
end
61 changes: 59 additions & 2 deletions src/SpaceDataModel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export AbstractDataVariable
export Project, Instrument, DataSet, LDataSet, Product
export Event
export AbstractCoordinateSystem, AbstractCoordinateVector, getcsys
export getmeta, setmeta!, setmeta

include("utils.jl")
include("metadata.jl")
Expand All @@ -25,8 +26,25 @@ include("coord.jl")
include("workload.jl")

# Interface
name(v) = @getfield v :name get(meta(v), "name", "")
meta(v) = @getfield v (:meta, :metadata) NoMetadata()
name(v) = @getfield v :name getmeta(v, "name", "")

"""
getmeta(x)

Get metadata for object `x`. If `x` does not have metadata, return `NoMetadata()`.

"""
getmeta(x) = @getfield x (:meta, :metadata) NoMetadata()

"""
getmeta(x, key, default=nothing)

Get metadata value associated with object `x` for key `key`, or `default` if `key` is not present.
"""
getmeta(x, key, default=nothing) = get(meta(x), key, default)

meta(x) = getmeta(x) # not exported (to be removed)

units(v) = @get(v, "units", nothing)
times(v) = @getfield v (:times, :time)

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

"""
setmeta!(x, key => value, ...; symbolkey => value2, ...)
setmeta!(x, dict::AbstractDict)

Update metadata for object `x` in-place and return `x`. The metadata container must be mutable.

The arguments could be multiple key-value pairs or a dictionary of metadata; keyword arguments are also accepted.

# Examples
```julia
setmeta!(x, :units => "m/s", :source => "sensor")
setmeta!(x, Dict(:units => "m/s", :quality => "good"))
setmeta!(x; units="m/s", calibrated=true)
```

Throws an error if the metadata is not mutable. Use `setmeta` for immutable metadata.
"""
function setmeta! end

function setmeta!(x, args...; kw...)
m = meta(x)
ismutable(m) || error("Metadata is not mutable, use `setmeta` instead")
set!(m, args...; kw...)
return x
end

"""
setmeta(x, key => value, ...; symbolkey => value2, ...)
setmeta(x, dict::AbstractDict)

Update metadata for object `x` for key `key` to have value `value` and return `x`.
"""
function setmeta end

function setmeta(x, args::Pair...; kw...)
return @set x.metadata=_merge(meta(x), Dict(args...), kw)
end
setmeta(x, dict::AbstractDict) = @set x.metadata=_merge(meta(x), dict)

end
7 changes: 4 additions & 3 deletions src/metadata.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ Base.values(::NoMetadata) = ()
Base.iterate(::NoMetadata) = nothing

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

Base.haskey(::NoMetadata, args...) = false
Base.get(::NoMetadata, key, default=nothing) = default
Expand Down
11 changes: 11 additions & 0 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,14 @@ function colors(i)
end

print_name(io::IO, var) = printstyled(io, name(var); color=colors(7))

# like merge to avoid privacy issues
_merge(a, b...) = merge(a, b...)

function set!(d::AbstractDict, args::Pair...; kw...)
for (k, v) in args
d[k] = v
end
return merge!(d, kw)
end
set!(d::AbstractDict, dict::AbstractDict; kw...) = merge!(d, dict, kw)
19 changes: 19 additions & 0 deletions test/DimensionalData.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@testitem "DimensionalData Extension Tests" begin
using DimensionalData
# Create test data
A = DimArray(rand(10, 5), (X(1:10), Y(1:5)))
A_mutable = rebuild(A, metadata=Dict())

# Test with key-value pairs and keyword arguments
@test_nowarn setmeta(A, "string" => "supported", description="velocity", calibrated=true)
@test_throws ErrorException setmeta!(A, "string" => "supported", calibrated=true)
@test_nowarn setmeta!(A_mutable, "string" => "supported", calibrated=true)
@test A_mutable.metadata == Dict("string" => "supported", :calibrated => true)

# Test with Dict
meta_dict = Dict("string" => "supported", :calibrated => false)
@test_nowarn setmeta(A, meta_dict)
@test_nowarn setmeta(A_mutable, meta_dict)
@test_nowarn setmeta!(A_mutable, meta_dict)
@test A_mutable.metadata == meta_dict
end
1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[deps]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
CheckConcreteStructs = "73c92db5-9da6-4938-911a-6443a7e94a58"
DimensionalData = "0703355e-b756-11e9-17c0-8b28908087d0"
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
Expand Down
2 changes: 1 addition & 1 deletion test/product.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

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

Expand Down