From 57ccc4860c0d9b1238953a647b637e08f8a2a9b5 Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Thu, 30 Jul 2026 06:42:30 -0400 Subject: [PATCH] Declare the documented non-exported API `public` `SBML.Model`, the `SBML.Math` AST types, and the documented data accessors are the package's user-facing interface, but none of them are exported -- `using SBML` would otherwise inject generic names like `Model`, `Species` and `Version` into the caller's namespace. That leaves downstream packages with no machine-checkable way to tell intended API from internals, and tools like ExplicitImports flag every `SBML.Model` as reaching into a private name. Declare those names `public` so `Base.ispublic` reflects what the manual already documents. `public` needs Julia 1.11 and this package supports 1.6, so the declaration is an `eval(Expr(:public, ...))` behind a version guard; a bare `public ...` would be a syntax error on older versions. Parser helpers (`get_optional_*`, `get_string`, `_readSBML`, ...), the `Maybe` combinators, `sbml`, and the math helpers in `math.jl` are left alone, since they are implementation details that happen to have docstrings. Co-Authored-By: Chris Rackauckas --- docs/src/functions.md | 16 +++++++++ src/SBML.jl | 75 +++++++++++++++++++++++++++++++++++++++++++ test/public.jl | 74 ++++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 1 + 4 files changed, 166 insertions(+) create mode 100644 test/public.jl diff --git a/docs/src/functions.md b/docs/src/functions.md index 3f946f95..531fb509 100644 --- a/docs/src/functions.md +++ b/docs/src/functions.md @@ -1,4 +1,20 @@ +## What counts as public API + +A name is public if it is exported, or if it is declared `public`. On Julia +1.11 and newer both cases answer `true` to `Base.ispublic(SBML, name)`, which +is the authoritative check. Anything else — including some parser and math +helpers that this reference still renders, such as the +[internal math helpers](#Internal-math-helpers) — may change in any release. + +Much of the public API is deliberately *not* exported, so that `using SBML` +does not bring generic names such as `Model`, `Species` or `Version` into your +namespace. Reach those through the module instead: `SBML.Model`, +`SBML.MathApply`, `SBML.extensive_kinetic_math`, and so on. + +Julia has no field-level visibility, so a public struct type also makes its +documented fields (listed under each type below) part of the public API. + # Data types ## Helper types diff --git a/src/SBML.jl b/src/SBML.jl index bd4bc5fc..599e51a6 100644 --- a/src/SBML.jl +++ b/src/SBML.jl @@ -34,6 +34,81 @@ export writeSBML export set_level_and_version, libsbml_convert, convert_simplify_math, convert_promotelocals_expandfuns +# The names below are documented API that is deliberately not exported, to keep +# `using SBML` from polluting the namespace with generic names like `Model`, +# `Parameter` or `Version`. Downstream packages are expected to reach them as +# `SBML.Model` etc., so they are declared `public`. +# +# `public` is a Julia 1.11 feature and this package still supports 1.6, so the +# declaration goes through `eval` behind a version guard; a bare `public ...` +# would be a syntax error on older versions. Julia has no field-level +# visibility, so for the struct types here the documented fields (rendered from +# `$(TYPEDFIELDS)`) are covered by the type being public. +@static if VERSION >= v"1.11" + eval( + Expr( + :public, + # types.jl + :Maybe, + :VPtr, + # structs.jl + :SBMLObject, + :UnitPart, + :UnitDefinition, + :GeneProductAssociation, + :GPARef, + :GPAAnd, + :GPAOr, + :Math, + :MathVal, + :MathIdent, + :MathConst, + :MathTime, + :MathAvogadro, + :MathApply, + :MathLambda, + :CVTerm, + :Parameter, + :Compartment, + :SpeciesReference, + :Reaction, + :Rule, + :AlgebraicRule, + :AssignmentRule, + :RateRule, + :Constraint, + :Species, + :GeneProduct, + :FunctionDefinition, + :EventAssignment, + :Trigger, + :Objective, + :Event, + :Member, + :Group, + :Model, + # version.jl + :Version, + # interpret.jl + :interpret_math, + :default_function_mapping, + :default_constants, + # unitful.jl + :unitful, + # utils.jl + :extensive_kinetic_math, + :fbc_flux_objective, + :kinetic_flux_objective, + :get_compartment_size, + :initial_amounts, + :initial_concentrations, + :isfreein, + :seemsdefined, + :test_suite_url, + ), + ) +end + # Read a file at precompile time, to improve time-to-first `readSBML`. writeSBML(readSBML(joinpath(@__DIR__, "..", "test", "data", "Dasgupta2020-written.xml"))) diff --git a/test/public.jl b/test/public.jl new file mode 100644 index 00000000..963e376e --- /dev/null +++ b/test/public.jl @@ -0,0 +1,74 @@ +const PUBLIC_NOT_EXPORTED = [ + :Maybe, + :VPtr, + :SBMLObject, + :UnitPart, + :UnitDefinition, + :GeneProductAssociation, + :GPARef, + :GPAAnd, + :GPAOr, + :Math, + :MathVal, + :MathIdent, + :MathConst, + :MathTime, + :MathAvogadro, + :MathApply, + :MathLambda, + :CVTerm, + :Parameter, + :Compartment, + :SpeciesReference, + :Reaction, + :Rule, + :AlgebraicRule, + :AssignmentRule, + :RateRule, + :Constraint, + :Species, + :GeneProduct, + :FunctionDefinition, + :EventAssignment, + :Trigger, + :Objective, + :Event, + :Member, + :Group, + :Model, + :Version, + :interpret_math, + :default_function_mapping, + :default_constants, + :unitful, + :extensive_kinetic_math, + :fbc_flux_objective, + :kinetic_flux_objective, + :get_compartment_size, + :initial_amounts, + :initial_concentrations, + :isfreein, + :seemsdefined, + :test_suite_url, +] + +@testset "Public API" begin + @testset "$name is defined and documented" for name in PUBLIC_NOT_EXPORTED + @test isdefined(SBML, name) + @test haskey(Docs.meta(SBML), Docs.Binding(SBML, name)) + end + + if VERSION >= v"1.11" + @testset "$name is public and unexported" for name in PUBLIC_NOT_EXPORTED + @test Base.ispublic(SBML, name) + @test !Base.isexported(SBML, name) + end + + @testset "every name reachable by `using SBML` is public" begin + for name in names(SBML) + name === :SBML && continue + @test Base.ispublic(SBML, name) + end + end + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 6ba238dc..011617e8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -20,6 +20,7 @@ include("common.jl") @testset "SBML test suite" begin include("version.jl") + include("public.jl") if TEST_SYMBOLICS # this defines a few functions used also in loadmodels.jl