pypi sbmlutils 0.13.0

5 hours ago

Release notes for sbmlutils 0.13.0

sbmlutils

We are pleased to release the next version of sbmlutils including the following changes. This release is the result of a full review of the repository (#486, #487, #488, #489, #490). It fixes wrong unit definitions, validation results and several silent failures, hardens code generation, the report server and downloads, makes the package smaller and the release process safer, moves all output of the library to logging and splits the model factory into a package. Code which uses one of the items under breaking changes has to be changed; the items under behaviour changes keep the API but change what is written, reported or raised.

Breaking changes

  • library code no longer prints: validation and flatten reports, the output of create_model(show_sbml=True), ReactionEquation.info(), xpp conversion headers, BioModels query progress, annotation results and Cytoscape information go to the sbmlutils.* loggers at the matching level. Nothing is shown unless the application configures logging or calls sbmlutils.log.enable_rich_logging(), see Validation (#489)
  • read_sbml raises a ValueError for a source it cannot read, i.e. a missing file or XML which is not well formed, and sbml_to_model, flatten_sbml, merge_models and ModelAnnotator raise a ValueError for a document without a model. They used to log the error and continue with an empty document, which failed later with an AttributeError or wrote an empty model. read_sbml no longer claims to read URLs, which it never did (#486)
  • ValidationResult.errors and .warnings hold SBMLErrorInfo snapshots instead of libsbml.SBMLError objects. The libsbml objects pointed into the error log of the document and returned garbage once the document was freed. The snapshots have the same getters for id, severity, category, line, column, message and package and isError(), isWarning(), isFatal(), but isinstance(e, libsbml.SBMLError) is False (#486)
  • antimony_to_sbml treats a Path as a file and a str as a file only if it names an existing file, otherwise as antimony content; invalid antimony raises a ValueError with the antimony error, and a missing file raises a FileNotFoundError. A path containing "model" used to be parsed as antimony text, and an error was logged on success while a failure passed silently (#486)
  • a second rule or a second initial assignment for the same symbol, an annotation entry which is neither an Annotation nor a tuple, None in a list of objects and an unsupported type in Model(objects=...) raise a ValueError; they used to produce an invalid model, a duplicated annotation or were dropped in silence (#486)
  • reaction equations support scientific notation (1e-3 A => B) and A+B without spaces; more than one reaction arrow, - as a separator and a modifier list which is not a single list at the end raise a ValueError, and EquationException is a ValueError. A => B => C used to drop C in silence (#486)
  • interpolation methods are the InterpolationMethod StrEnum (the old constants remain as aliases) and an unknown method raises a ValueError (#486)
  • removed, each verified unused: fbc.set_flux_bounds (broken), fbc.set_boundary_conditions_false, comp.get_submodel_frameworks, replace_elements, replace_element_in_submodels, replaced_by, SBASE_REF_TYPE_*, flatten.flatten_external_model_definitions, Units.create_unit_definitions (deprecated), xpp_helpers.ast_info, converters.mathml.evaluateMathML with its helpers and the star import of math, and report.mathml.xslt_cmml2pmml/xslt_pmml2tex, which are the compiled XSLT_CMML2PMML/XSLT_PMML2TEX now (#488, #489)
  • report.sbmlreport.start_server(path, port) takes the model file, binds 127.0.0.1, serves only that file and returns the running server instead of blocking. create_online_report therefore needs an sbml4humans running on the same machine, its default server is http://localhost:3456, and it opens the current route /report?url= (#488)
  • the assignments parameter of SBMLDocumentInfo.compartments(), species() and parameters() is removed, and read_layout_xml drops its unused sbml_path parameter (#486)
  • create_sbml of LocalParameter, KineticLaw, UserDefinedConstraintComponent, FluxObjective, UncertParameter and UncertSpan requires the model it is written into instead of falling back to getModel(), which answers with the wrong model inside a comp model definition. AlgebraicRule is no longer a RuleWithVariable (#490)
  • pydantic is no longer a dependency of sbmlutils (#487)

Behaviour changes

  • unit definitions with a prefix under an exponent or with a magnitude get correct multipliers: mm**2 was written as 1e-3 m², cm**3 as 0.01 m³, 10/l as 0.1/l and 1e-3/min as 16.7/s. Every unit is written as (multiplier * 10^scale * kind)^exponent and checked against pint. A magnitude without a real root raises a ValueError (#486)
  • validation reads only the errors each check added to the error log, reports the read errors of a document once and leaves the error log of the document as it found it. Errors used to be misreported, counted twice or missed, a document with read errors was reported valid, and validating a document twice made the counts grow. validate_sbml reports malformed XML as read errors and raises a FileNotFoundError only for a path which does not exist and an IsADirectoryError for a directory (#486)
  • flatten_sbml and merge_models no longer change the working directory, which broke relative paths and is not thread safe; libsbml resolves external model definitions from the absolute location of the document. merge_models accepts str paths, honours sbml_level and sbml_version, no longer changes the dict it is given and points a submodel at the id of its external model definition. create_ExternalModelDefinition takes an optional model_ref and sets no modelRef by default (#486)
  • files in directories with non-ASCII characters are read and written on every platform: libsbml cannot open such a path on Windows, so these files are read and written by python and only parsed and serialized by libsbml. External model definitions in such a directory still cannot be resolved on Windows, a libsbml limitation (#486)
  • unit strings of reports render magnitudes as numbers (160 s, 2.1 g, mmol/(160 s)) and fractional exponents (s^0.5); 160 s used to render as 1min and 2.1 g as 2.g (#486)
  • elements without id or metaid get unique and stable primary keys in SBMLDocumentInfo, e.g. SpeciesReference:R1/listOfReactants/0, instead of SHA1 digests which collided for identical elements (#486)
  • add_default_flux_bounds creates unique parameter ids (lower_1) instead of duplicating existing lower and upper ids (#486)
  • the python code generated by SBML2ODE runs: the math is translated on the libsbml AST (piecewise, logical operators, relations, ln, rem, quotient, xor, INF, NaN), checked against roadrunner. Initial assignments are ignored with a warning; local parameters, function definitions, delay and rateOf raise a NotImplementedError; python keywords used as ids get a trailing underscore; cyclic assignment rules raise a ValueError instead of recursing endlessly (#488)
  • names and units are written on a single line in every generated language, LaTeX escapes ids, names and units, and python, R and julia export raise a ValueError for ids which are not SIds, so a name can no longer break out of a comment into executable code (#488)
  • download_biomodel_sbml raises a ValueError for a manifest location outside output_dir and creates subdirectories for nested locations (#488)
  • sbmlutils.factory is a package (_core, units, core_elements, distrib, fbc, comp, model). from sbmlutils.factory import * and every public name are unchanged; names the old module only imported (BQB, SBO, write_sbml, sbml_to_antimony, ...) still resolve with a DeprecationWarning naming their proper import. Classes name their submodule in __module__ and repr, so pickles written by this version cannot be read by older versions; logger names are per submodule, children of sbmlutils.factory (#490)
  • the notes of a species reference are normalized like those of every other element, and an invalid SId of a species reference is reported like every other element. UnitDefinition has a readable repr (#490)
  • sbmlutils.console.console no longer records all output in memory (#489)
  • annotate_sbml raises an OSError for an annotations path which is not a file (#489)
  • the examples registry moved from examples/__init__.py to examples/registry.py, so python -m examples.X no longer warns; the examples enable rich logging in their __main__ block (#489)

Fixes

  • the tests never reach a running Cytoscape: running the test suite on a machine with Cytoscape open closed its session without saving. visualize_sbml logs a CyError instead of raising it, and visualize_antimony returns the network SUID (#486)
  • Interpolator compared the method with is, which failed for an equal string, and the cubic spline of unsorted data was wrong (#486)
  • xpp: the function names of min and max were swapped and notes were escaped twice (#486)
  • the online report served the whole directory of the model with listings on all network interfaces, from a server which never stopped in a long running process (#488)
  • the hierarchical model of the COMBINE archive example referenced itself instead of the minimal model, so creating the archive failed (#490)
  • typing.get_type_hints works for every public class of sbmlutils.factory (#490)

Packaging and development

  • the sdist holds only the package, tests, examples and the scripts the tests use, 3.6 MB instead of 23 MB, and the tests of an unpacked sdist skip what needs the SBML test suite (#487)
  • the package ships py.typed and the Typing :: Typed classifier (#487)
  • dependencies: lxml>=6.1.0 (CVE-2026-41066) and pandas>=2.2.2; the floors of rich, requests and markdown-it-py are lowered to verified versions. The tox environment lowest tests every direct dependency at its floor in CI (#487)
  • uv.lock is committed, Dependabot updates the lock and the pre-commit hooks, and ruff in CI reads its version from the lock (#487)
  • the release workflow builds without write permissions, publishes with attestations through trusted publishing from a job which only holds id-token: write, checks the release notes before publishing, and every action is pinned to a commit SHA (#487)
  • ruff enforces PERF, PTH, ARG, S, PT, TRY201, PLW0120 and PLC0206, and the tests assert what they claim; new pytest markers slow and network (#489)
  • the model factory is simpler: shared base classes for the rules, class-level authoring hints, one code path for the fields of a species reference, one walk to find the packages of a model and an id index for the rules of a model. The written SBML is unchanged, 890 documents byte for byte (#490)

Don't miss a new sbmlutils release

NewReleases is sending notifications on new releases.