diff --git a/docs/src/utilities.jl b/docs/src/utilities.jl index c0a3bda..2824775 100644 --- a/docs/src/utilities.jl +++ b/docs/src/utilities.jl @@ -14,6 +14,8 @@ A.accessors() @test length(A.accessors()) == 35 #src +@test length(A.required_functions()) == 11 + # You do not need to overload all of them (e.g., if you model does not have any # genes you can completely omit all gene-related functions). The main required # ones (esp. the reaction- and metabolite-related ones) will throw an error if diff --git a/src/AbstractFBCModels.jl b/src/AbstractFBCModels.jl index 98f3e94..b51c643 100644 --- a/src/AbstractFBCModels.jl +++ b/src/AbstractFBCModels.jl @@ -30,9 +30,9 @@ module AbstractFBCModels using DocStringExtensions include("types.jl") +include("utils.jl") include("accessors.jl") include("io.jl") -include("utils.jl") include("testing.jl") include("canonical.jl") diff --git a/src/accessors.jl b/src/accessors.jl index 0e6ccc5..96f978a 100644 --- a/src/accessors.jl +++ b/src/accessors.jl @@ -10,7 +10,7 @@ modeling, such as metabolite exchanges, separated forward and reverse reaction directions, supplies of enzymatic and genetic material and virtual cell volume, etc. """ -reactions(a::AbstractFBCModel)::Vector{String} = unimplemented(typeof(a), :reactions) +@required reactions(a::AbstractFBCModel)::Vector{String} """ $(TYPEDSIGNATURES) @@ -19,7 +19,7 @@ The number of reactions in the given model. Must be equal to the length of the vector returned by [`reactions`](@ref), and may be more efficient for just determining the size. """ -n_reactions(a::AbstractFBCModel)::Int = unimplemented(typeof(a), :n_reactions) +@required n_reactions(a::AbstractFBCModel)::Int """ $(TYPEDSIGNATURES) @@ -29,7 +29,7 @@ Return a vector of metabolite identifiers in a model. As with [`reactions`](@ref), some metabolites in models may be virtual, representing purely technical equality constraints. """ -metabolites(a::AbstractFBCModel)::Vector{String} = unimplemented(typeof(a), :metabolites) +@required metabolites(a::AbstractFBCModel)::Vector{String} """ $(TYPEDSIGNATURES) @@ -39,7 +39,7 @@ the vector returned by [`metabolites`](@ref), and may be more efficient for just determining the size. """ function n_metabolites end -n_metabolites(a::AbstractFBCModel)::Int = unimplemented(typeof(a), :n_metabolites) +@required n_metabolites(a::AbstractFBCModel)::Int """ $(TYPEDSIGNATURES) @@ -49,7 +49,7 @@ Return identifiers of all genes contained in the model. Empty if none. Genes are also sometimes called "gene products" but we write genes for simplicity. """ -genes(a::AbstractFBCModel)::Vector{String} = unimplemented(typeof(a), :genes) +@required genes(a::AbstractFBCModel)::Vector{String} """ $(TYPEDSIGNATURES) @@ -60,7 +60,7 @@ by [`genes`](@ref)). This may be more efficient than calling [`genes`](@ref) and measuring the array. """ -n_genes(a::AbstractFBCModel)::Int = unimplemented(typeof(a), :n_genes) +@required n_genes(a::AbstractFBCModel)::Int """ $(TYPEDSIGNATURES) @@ -94,7 +94,7 @@ The sparse stoichiometric matrix of a given model. This usually corresponds to all the equality constraints in the model. The matrix must be of size [`n_metabolites`](@ref) by [`n_reactions`](@ref). """ -stoichiometry(a::AbstractFBCModel)::SparseMat = unimplemented(typeof(a), :stoichiometry) +@required stoichiometry(a::AbstractFBCModel)::SparseMat """ $(TYPEDSIGNATURES) @@ -112,8 +112,7 @@ $(TYPEDSIGNATURES) Lower and upper bounds of all reactions in the model. """ -bounds(a::AbstractFBCModel)::Tuple{Vector{Float64},Vector{Float64}} = - unimplemented(typeof(a), :bounds) +@required bounds(a::AbstractFBCModel)::Tuple{Vector{Float64},Vector{Float64}} """ $(TYPEDSIGNATURES) @@ -138,7 +137,7 @@ $(TYPEDSIGNATURES) The objective vector of the model. """ -objective(a::AbstractFBCModel)::SparseVec = unimplemented(typeof(a), :objective) +@required objective(a::AbstractFBCModel)::SparseVec """ $(TYPEDSIGNATURES) @@ -189,8 +188,7 @@ maps the metabolite IDs to their stoichiometric coefficients. Using this function may be more efficient in cases than loading the whole [`stoichiometry`](@ref). """ -reaction_stoichiometry(a::AbstractFBCModel, reaction_id::String)::Dict{String,Float64} = - unimplemented(typeof(a), :reaction_stoichiometry) +@required reaction_stoichiometry(a::AbstractFBCModel, reaction_id::String)::Dict{String,Float64} """ $(TYPEDSIGNATURES) diff --git a/src/io.jl b/src/io.jl index 343539e..4285c0d 100644 --- a/src/io.jl +++ b/src/io.jl @@ -13,7 +13,7 @@ $(TYPEDSIGNATURES) Save a model to the given path. """ -save(a::AbstractFBCModel, path::String)::Nothing = unimplemented(typeof(a), :save) +@required save(a::AbstractFBCModel, path::String) """ $(TYPEDSIGNATURES) diff --git a/src/utils.jl b/src/utils.jl index bc4a10d..9e62b7b 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -3,6 +3,26 @@ import Downloads: download import SHA: sha256 import InteractiveUtils: methodswith + +const REQUIRED_FUNCTIONS = Function[] +macro required(sig) + call_ex = sig.head == :(::) ? sig.args[1] : sig + call_ex.head == :call || error("malformed signature definition") + name = call_ex.args[1] + name isa Symbol || error("malformed signature definition") + + model_arg_ex = call_ex.args[2] + model_arg_ex.head == :(::) || error("malformed signature definition") + model_arg = model_arg_ex.args[1] + model_arg isa Symbol || error("malformed signature definition") + + return esc(quote + Base.@__doc__ $call_ex = $unimplemented(typeof($model_arg), $(Meta.quot(name))) + push!(REQUIRED_FUNCTIONS, $name) + $name + end) +end + unimplemented(t::Type, x::Symbol) = error("AbstractFBCModels interface method $x is not implemented for type $t") @@ -10,11 +30,13 @@ unimplemented(t::Type, x::Symbol) = $(TYPEDSIGNATURES) Provide a `methodswith`-style listing of accessors that the model implementors -should implement. +may implement. For typesystem reasons, the list **will not contain** methods for [`save`](@ref) and [`filename_extensions`](@ref) that dispatch on type objects. You should implement these as well. + +See also [`required_functions`](@ref) for the minimal list that must be implemented. """ function accessors() ms = Method[] @@ -27,6 +49,30 @@ function accessors() return ms end +""" +$(TYPEDSIGNATURES) + +Provide a `methodswith`-style listing of functions that the model implementors +must implement to have a functional `AbstractFBCModel`. +constrast this to the longer list of items that are returned by [`accessors`](@ref). +The extra elements have sensible defaults based on these required functions. +Where-as not defining these methods will result in errors. +Though depending on your models capability relying on those defaults may mean some +functionality is hidden. +(e.g. default [`coupling`](@ref) if you don't implement that is to assume none) + +""" +function required_functions() + ms = Method[] + for f = REQUIRED_FUNCTIONS + methodswith(AbstractFBCModels.AbstractFBCModel, f, ms) + end + return ms +end + + + + """ $(TYPEDSIGNATURES)