Plugins & Advice
In DataToolkit, the plugin system enables key behaviour to be completely transformed when operating on a given DataCollection.
DataToolkitCore.Plugin — TypePluginA named collection of Advice that accompanies DataCollections.
The complete collection of advice provided by all plugins of a DataCollection is applied to every @advised call involving the DataCollection.
See also: Advice, AdviceAmalgamation.
Construction
Plugin(name::String, advisors::Vector{Advice}) -> Plugin
Plugin(name::String, advisors::Vector{<:Function}) -> PluginDataToolkitCore.@dataplugin — Macro@dataplugin plugin_variable
@dataplugin plugin_variable :defaultRegister the plugin given by the variable plugin_variable, along with its documentation (fetched by @doc). Should :default be given as the second argument the plugin is also added to the list of default plugins.
This effectively serves as a minor, but appreciable, convenience for the following pattern:
push!(PLUGINS, myplugin)
PLUGINS_DOCUMENTATION[myplugin.name] = @doc myplugin
push!(DEFAULT_PLUGINS, myplugin.name) # when also adding to defaultsAdvice
Inspired by Lisp, DataToolkitCore comes with a method of completely transforming its behaviour at certain defined points. This is essentially a restricted form of Aspect-oriented programming. At certain declared locations (termed "join points"), we consult a list of "advise" functions that modify the execution at that point, and apply the (matched via "pointcuts") advise functions accordingly.
Each applied advise function is wrapped around the invocation of the join point, and is able to modify the arguments, execution, and results of the join point.
DataToolkitCore.Advice — TypeAdvice{func, context} <: FunctionAdvices allow for composable, highly flexible modifications of data by encapsulating a function call. They are inspired by elisp's advice system, namely the most versatile form — :around advice, and Clojure's transducers.
A Advice is essentially a function wrapper, with a priority::Int attribute. The wrapped functions should be of the form:
(action::Function, args...; kargs...) ->
([post::Function], action::Function, args::Tuple, [kargs::NamedTuple])Short-hand return values with post or kargs omitted are also accepted, in which case default values (the identity function and (;) respectively) will be automatically substituted in.
input=(action args kwargs)
┃ ┏╸post=identity
╭─╂────advisor 1────╂─╮
╰─╂─────────────────╂─╯
╭─╂────advisor 2────╂─╮
╰─╂─────────────────╂─╯
╭─╂────advisor 3────╂─╮
╰─╂─────────────────╂─╯
┃ ┃
▼ ▽
action(args; kargs) ━━━━▶ post╺━━▶ resultTo specify which transforms a Advice should be applied to, ensure you add the relevant type parameters to your transducing function. In cases where the transducing function is not applicable, the Advice will simply act as the identity function.
After all applicable Advices have been applied, action(args...; kargs...) |> post is called to produce the final result.
The final post function is created by rightwards-composition with every post entry of the advice forms (i.e. at each stage post = post ∘ extra is run).
The overall behaviour can be thought of as shells of advice.
╭╌ advisor 1 ╌╌╌╌╌╌╌╌─╮
┆ ╭╌ advisor 2 ╌╌╌╌╌╮ ┆
┆ ┆ ┆ ┆
input ━━┿━┿━━━▶ function ━━━┿━┿━━▶ result
┆ ╰╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╯ ┆
╰╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╯Constructors
Advice(priority::Int, f::Function)
Advice(f::Function) # priority is set to 1Examples
1. Logging every time a DataSet is loaded.
loggingadvisor = Advice(
function(post::Function, f::typeof(load), loader::DataLoader, input, outtype)
@info "Loading $(loader.data.name)"
(post, f, (loader, input, outtype))
end)2. Automatically committing each data file write.
writecommitadvisor = Advice(
function(post::Function, f::typeof(write), writer::DataWriter{:filesystem}, output, info)
function writecommit(result)
run(`git add $output`)
run(`git commit -m "update $output"`)
result
end
(post ∘ writecommit, writefn, (output, info))
end)DataToolkitCore.AdviceAmalgamation — TypeAdviceAmalgamationAn AdviceAmalgamation is a collection of Advices sourced from available Plugins.
Like individual Advices, an AdviceAmalgamation can be called as a function. However, it also supports the following convenience syntax:
(::AdviceAmalgamation)(f::Function, args...; kargs...) # -> resultConstructors
AdviceAmalgamation(advisors::Vector{Advice}, plugins_wanted::Vector{String}, plugins_used::Vector{String})
AdviceAmalgamation(plugins::Vector{String})
AdviceAmalgamation(collection::DataCollection)DataToolkitCore.@advise — Macro@advise [source] f(args...; kwargs...) [::T]Convert a function call f(args...; kwargs...) to an advised function call, where the advise collection is obtained from source or the first data-like* value of args.
* i.e. a DataCollection, DataSet, or DataTransformer
For example, @advise myfunc(other, somedataset, rest...) is equivalent to somedataset.collection.advise(myfunc, other, somedataset, rest...).
This macro performs a fairly minor code transformation, but should improve clarity.
Unless otherwise asserted, it is assumed that the advised function will have the same return type as the original function. If this assumption does not hold, make sure to add a type assertion (even just ::Any).
Advisement (join) points
Parsing and serialisation of data sets and collections
DataCollections, DataSets, and DataTransformers are advised at two stages during parsing:
- When calling
fromspecon theDictrepresentation, at the start of parsing - At the end of the
fromspecfunction, callingidentityon the object
Serialisation is performed through the tospec call, which is also advised.
The signatures of the advised function calls are as follows:
fromspec(DataCollection, spec::Dict{String, Any}; path::Union{String, Nothing})::DataCollection
identity(collection::DataCollection)::DataCollection
tospec(collection::DataCollection)::Dictfromspec(DataSet, collection::DataCollection, name::String, spec::Dict{String, Any})::DataSet
identity(dataset::DataSet)::DataSet
tospec(dataset::DataSet)::Dictfromspec(DT::Type{<:DataTransformer}, dataset::DataSet, spec::Dict{String, Any})::DT
identity(dt::DataTransformer)::DataTransformer
tospec(dt::DataTransformer)::DictProcessing identifiers
Both the parsing of an Identifier from a string, and the serialisation of an Identifier to a string are advised. Specifically, the following function calls:
parse_ident(spec::AbstractString)
string(ident::Identifier)The data flow arrows
The reading, writing, and storage of data may all be advised. Specifically, the following function calls:
load(loader::DataLoader, datahandle, as::Type)
storage(provider::DataStorage, as::Type; write::Bool)
save(writer::DataWriter, datahandle, info)Index of advised calls (join points)
There are 33 advised function calls, across 10 files, covering 12 functions (automatically detected).
Arranged by function
create (1 instance)
creation.jl
- On line 47
create(DataCollection, dc)is advised within acreate!method.
- On line 47
fromspec (4 instances)
creation.jl
- On line 96
fromspec(DataSet, parent, String(name), toml_safe(parent, spec))is advised within acreatemethod. - On line 159
fromspec(T, parent, toml_safe(parent, spec))is advised within acreatemethod.
- On line 96
parser.jl
- On line 124
fromspec(DT, dataset, spec)is advised within aDT::Type{<:DataTransformer}method. - On line 259
fromspec(DataSet, collection, name, spec)is advised within aDataSetmethod.
- On line 124
identity (5 instances)
creation.jl
- On line 10
identity(collection)is advised within aDataCollectionmethod.
- On line 10
manipulation.jl
- On line 110
identity(collection)is advised within acollection_reinit!method.
- On line 110
parser.jl
- On line 171
identity(DT(dataset, ttype, priority, dataset_parameters(dataset, Val(:extract), parameters)))is advised within afromspec(DT::Type{<:DataTransformer}, dataset::DataSet, spec::Dict{String, Any})method. - On line 251
identity(collection)is advised within afromspecmethod. - On line 291
identity(dataset)is advised within afromspecmethod.
- On line 171
lint (1 instance)
lint.jl
- On line 84
lint(obj, linters)is advised within alint(obj::T)method.
- On line 84
load (2 instances)
externals.jl
- On line 200
load(loader, datahandle, Tloader_out)is advised within aread1method. - On line 215
load(loader, nothing, as)is advised within aread1method.
- On line 200
parse_ident (8 instances)
externals.jl
- On line 83
parse_ident(identstr)is advised within adatasetmethod. - On line 89
parse_ident(identstr)is advised within adatasetmethod.
- On line 83
errors.jl
- On line 44
parse_ident(err.identifier)is advised within aBase.showerrormethod. - On line 53
parse_ident(err.identifier)is advised within aBase.showerrormethod.
- On line 44
identification.jl
- On line 200
parse_ident(identstr)is advised within aresolvemethod. - On line 204
parse_ident(identstr)is advised within aresolvemethod.
- On line 200
parameters.jl
- On line 41
parse_ident(dsid_match.captures[1])is advised within adataset_parametersmethod.
- On line 41
parser.jl
- On line 74
parse_ident(spec)is advised within aBase.parsemethod.
- On line 74
read1 (1 instance)
externals.jl
- On line 153
read1(dataset, as)is advised within aBase.read(dataset::DataSet, #= ../../src/interaction/externals.jl:150 =# @nospecialize(as::Type))method.
- On line 153
refine (1 instance)
identification.jl
- On line 173
refine(matchingdatasets, ident, String[])is advised within arefinemethod.
- On line 173
save (1 instance)
externals.jl
- On line 377
save(writer, datahandle, info)is advised within aBase.writemethod.
- On line 377
storage (1 instance)
externals.jl
- On line 289
storage(storage_provider, Tout; write)is advised within aBase.open(data::DataSet, #= ../../src/interaction/externals.jl:286 =# @nospecialize(as::Type); write::Bool = false)method.
- On line 289
string (5 instances)
display.jl
- On line 17
string(nameonly)is advised within aBase.showmethod.
- On line 17
errors.jl
- On line 82
string(ident)is advised within aBase.showerrormethod. - On line 90
string(ident)is advised within aBase.showerrormethod.
- On line 82
identification.jl
- On line 128
string(ident)is advised within aresolvemethod.
- On line 128
parameters.jl
- On line 58
string(ident)is advised within adataset_parametersmethod.
- On line 58
tospec (3 instances)
writer.jl
- On line 59
tospec(dt)is advised within aBase.convertmethod. - On line 84
tospec(ds)is advised within aBase.convertmethod. - On line 98
tospec(dc)is advised within aBase.convertmethod.
- On line 59
Arranged by file
creation.jl (4 instances)
- On line 10
identity(collection)is advised within aDataCollectionmethod. - On line 47
create(DataCollection, dc)is advised within acreate!method. - On line 96
fromspec(DataSet, parent, String(name), toml_safe(parent, spec))is advised within acreatemethod. - On line 159
fromspec(T, parent, toml_safe(parent, spec))is advised within acreatemethod.
display.jl (1 instance)
- On line 17
string(nameonly)is advised within aBase.showmethod.
externals.jl (7 instances)
- On line 83
parse_ident(identstr)is advised within adatasetmethod. - On line 89
parse_ident(identstr)is advised within adatasetmethod. - On line 153
read1(dataset, as)is advised within aBase.read(dataset::DataSet, #= ../../src/interaction/externals.jl:150 =# @nospecialize(as::Type))method. - On line 200
load(loader, datahandle, Tloader_out)is advised within aread1method. - On line 215
load(loader, nothing, as)is advised within aread1method. - On line 289
storage(storage_provider, Tout; write)is advised within aBase.open(data::DataSet, #= ../../src/interaction/externals.jl:286 =# @nospecialize(as::Type); write::Bool = false)method. - On line 377
save(writer, datahandle, info)is advised within aBase.writemethod.
lint.jl (1 instance)
- On line 84
lint(obj, linters)is advised within alint(obj::T)method.
manipulation.jl (1 instance)
- On line 110
identity(collection)is advised within acollection_reinit!method.
errors.jl (4 instances)
- On line 44
parse_ident(err.identifier)is advised within aBase.showerrormethod. - On line 53
parse_ident(err.identifier)is advised within aBase.showerrormethod. - On line 82
string(ident)is advised within aBase.showerrormethod. - On line 90
string(ident)is advised within aBase.showerrormethod.
identification.jl (4 instances)
- On line 128
string(ident)is advised within aresolvemethod. - On line 173
refine(matchingdatasets, ident, String[])is advised within arefinemethod. - On line 200
parse_ident(identstr)is advised within aresolvemethod. - On line 204
parse_ident(identstr)is advised within aresolvemethod.
parameters.jl (2 instances)
- On line 41
parse_ident(dsid_match.captures[1])is advised within adataset_parametersmethod. - On line 58
string(ident)is advised within adataset_parametersmethod.
parser.jl (6 instances)
- On line 74
parse_ident(spec)is advised within aBase.parsemethod. - On line 124
fromspec(DT, dataset, spec)is advised within aDT::Type{<:DataTransformer}method. - On line 171
identity(DT(dataset, ttype, priority, dataset_parameters(dataset, Val(:extract), parameters)))is advised within afromspec(DT::Type{<:DataTransformer}, dataset::DataSet, spec::Dict{String, Any})method. - On line 251
identity(collection)is advised within afromspecmethod. - On line 259
fromspec(DataSet, collection, name, spec)is advised within aDataSetmethod. - On line 291
identity(dataset)is advised within afromspecmethod.
writer.jl (3 instances)
- On line 59
tospec(dt)is advised within aBase.convertmethod. - On line 84
tospec(ds)is advised within aBase.convertmethod. - On line 98
tospec(dc)is advised within aBase.convertmethod.