# libsbgnpy > Python library for the Systems Biology Graphical Notation (SBGN) The complete documentation from https://matthiaskoenig.github.io/libsbgnpy, one section per page. --- ![libsbgnpy](images/libsbgnpy.png) # libsbgnpy: python library for SBGN [![GitHub Actions CI/CD Status](https://github.com/matthiaskoenig/libsbgnpy/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/matthiaskoenig/libsbgnpy/actions/workflows/ci-cd.yml) [![Documentation](https://img.shields.io/badge/docs-libsbgnpy-3f51b5.svg)](https://matthiaskoenig.github.io/libsbgnpy) [![Version](https://img.shields.io/pypi/v/libsbgnpy.svg)](https://pypi.org/project/libsbgnpy/) [![Python Versions](https://img.shields.io/pypi/pyversions/libsbgnpy.svg)](https://pypi.org/project/libsbgnpy/) [![MIT License](https://img.shields.io/pypi/l/libsbgnpy.svg)](https://opensource.org/licenses/MIT) [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.597155.svg)](https://doi.org/10.5281/zenodo.597155) `libsbgnpy` is a python library to work with the [Systems Biology Graphical Notation (SBGN)](https://sbgn.github.io/). The source code is available from [https://github.com/matthiaskoenig/libsbgnpy](https://github.com/matthiaskoenig/libsbgnpy). ## Background A pathway drawn by hand is a picture: a human sees what it means, a machine sees pixels. SBGN fixes this by defining what every shape in a biological map means ([Le Novère et al. 2009](https://doi.org/10.1038/nbt.1558)). A rounded rectangle is a macromolecule, a circle is a simple chemical, a square with two ports is a process, an arrow with a filled head is production. Three languages cover what is usually drawn: - **Process Description (PD)** — what is converted into what, e.g., a metabolic or a signalling network, - **Entity Relationship (ER)** — which entity influences which other entity, without ordering the events, - **Activity Flow (AF)** — the flow of activity between the entities, the level of a cartoon in a review. **SBGN-ML** is the exchange format of such a map ([van Iersel et al. 2012](https://doi.org/10.1186/1471-2105-13-S6-S12)): an XML document which stores the glyphs, the arcs between them, and the coordinates they are drawn at, so a map travels between the tools that draw it. `libsbgnpy` reads, writes, validates and renders these documents from python. ## Features - **[SBGN maps](maps.md)** — the complete SBGN-ML schema as python classes, generated with [xsdata](https://github.com/tefra/xsdata): maps, glyphs, arcs, ports and bounding boxes, with the classes of the three languages as enums. - **[Reading and writing](io.md)** — read and write SBGN-ML documents from files or strings; SBGN-ML 0.1 and 0.2 documents are upconverted while reading. - **[Notes and extensions](extensions.md)** — the arbitrary XML which SBGN elements carry, written and read back as XML. - **[Render information](render.md)** — colors, gradients and styles of a map, stored as an extension. - **[Validation](validation.md)** — validation of a document against the SBGN XSD schema. - **[Images](images.md)** — rendering of a map as a PNG through a web service. ## Quickstart Read an SBGN document and walk over its content: ```python from pathlib import Path from libsbgnpy import read_sbgn_from_file sbgn = read_sbgn_from_file(Path("examples/sbgn/adh.sbgn")) map = sbgn.map[0] print(map.language) # MapLanguage.PROCESS_DESCRIPTION for glyph in map.glyph: print(glyph.id, glyph.class_value, glyph.label.text if glyph.label else None) # glyph1 GlyphClass.SIMPLE_CHEMICAL Ethanol ``` Create a map from scratch, write it and render it: ```python from pathlib import Path from libsbgnpy import ( Bbox, Glyph, GlyphClass, Label, Map, MapLanguage, Sbgn, render_sbgn, write_sbgn_to_file, ) map = Map( id="ethanol", language=MapLanguage.PROCESS_DESCRIPTION, bbox=Bbox(x=0, y=0, w=363, h=253), ) map.glyph.append( Glyph( id="ethanol", class_value=GlyphClass.SIMPLE_CHEMICAL, label=Label(text="Ethanol"), bbox=Bbox(x=40, y=120, w=60, h=60), ) ) sbgn = Sbgn(map=[map]) write_sbgn_to_file(sbgn, Path("ethanol.sbgn")) render_sbgn(sbgn, Path("ethanol.png")) ``` The complete example, which builds the alcohol dehydrogenase reaction step by step, is [`examples/ethanol.py`](https://github.com/matthiaskoenig/libsbgnpy/blob/develop/examples/ethanol.py): ![ethanol example](images/ethanol_example.png) If you have any questions or issues please [open an issue](https://github.com/matthiaskoenig/libsbgnpy/issues). # For agents The documentation is published as markdown as well, following the [llms.txt convention](https://llmstxt.org/): - [llms.txt](https://matthiaskoenig.github.io/libsbgnpy/llms.txt) — an annotated index of every page - [llms-full.txt](https://matthiaskoenig.github.io/libsbgnpy/llms-full.txt) — the complete documentation in a single file - the markdown of every page next to its html, i.e., [io.md](https://matthiaskoenig.github.io/libsbgnpy/io.md) for [io](io.md) # How to cite [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.597155.svg)](https://doi.org/10.5281/zenodo.597155) If you use `libsbgnpy` please cite the archived software on Zenodo. [10.5281/zenodo.597155](https://doi.org/10.5281/zenodo.597155) is the concept DOI, which always resolves to the latest version; the DOI below is the one of this release: > König, M. (2026). *libsbgnpy: python library for SBGN* (Version 0.6.1) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.22642321 ```bibtex @software{konig_libsbgnpy, author = {König, Matthias}, title = {libsbgnpy: python library for SBGN}, year = {2026}, month = {9}, version = {0.6.1}, publisher = {Zenodo}, doi = {10.5281/zenodo.22642321}, url = {https://doi.org/10.5281/zenodo.22642321}, } ``` # License - Source Code: [MIT](https://opensource.org/license/MIT) - Documentation: [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) # Funding Matthias König (MK) was supported by the Federal Ministry of Education and Research (BMBF, Germany) within the research network Systems Medicine of the Liver (LiSyM, grant number 031L0054). MK is supported by the Federal Ministry of Education and Research (BMBF, Germany) within ATLAS by grant number 031L0304B and by the German Research Foundation (DFG) within the Research Unit Program FOR 5151 QuaLiPerF (Quantifying Liver Perfusion-Function Relationship in Complex Resection - A Systems Medicine Approach) by grant number 436883643 and by grant number 465194077 (Priority Programme SPP 2311, Subproject SimLivA). © 2016-2026 Matthias König --- # Installation `libsbgnpy` requires python >= 3.11 and is available from [pypi](https://pypi.python.org/pypi/libsbgnpy). ## With uv [uv](https://docs.astral.sh/uv/) is the recommended way to install the package. In a project it is added as a dependency, which resolves and locks it together with the rest of the environment: ```bash uv add libsbgnpy ``` Into an existing virtual environment it is installed through the pip interface of uv: ```bash uv venv uv pip install libsbgnpy ``` ## With pip ```bash pip install libsbgnpy ``` ## Development version The current state of the `develop` branch is installed directly from GitHub: ```bash uv add "libsbgnpy @ git+https://github.com/matthiaskoenig/libsbgnpy.git@develop" ``` or, with pip, ```bash pip install git+https://github.com/matthiaskoenig/libsbgnpy.git@develop ``` To work on the repository itself, with the test and documentation tooling, see [Development](development.md). ## Dependencies The package depends on [xsdata](https://github.com/tefra/xsdata), which reads and writes the SBGN-ML documents, [lxml](https://lxml.de/), which validates them against the schema, [requests](https://requests.readthedocs.io/), which queries the rendering web service, and [rich](https://rich.readthedocs.io/), which formats the output of the examples. `lxml` ships wheels for all common platforms, so no compiler is needed. ## Logging `libsbgnpy` does not configure logging. It logs to loggers below the `libsbgnpy` logger and leaves handlers, levels and formatting to the application, so the messages of the package stay under your control: ```python import logging logging.basicConfig(level=logging.INFO) logging.getLogger("libsbgnpy").setLevel(logging.WARNING) ``` For scripts and interactive work the rich output of the package can be turned on explicitly: ```python from libsbgnpy import log log.enable_rich_logging() ``` --- # SBGN maps The classes of `libsbgnpy.sbgn` are the python bindings of the SBGN-ML schema, generated with [xsdata](https://github.com/tefra/xsdata). They mirror the schema, so the [SBGN specifications](https://github.com/sbgn/sbgn/wiki/SBGN_Specifications) are the reference for what an element means; this page describes how the classes are used. ## The document An SBGN-ML document is an `Sbgn` object holding one or more `Map` objects. A map declares its language, an optional bounding box, and holds the glyphs and arcs it is drawn from: ```python from libsbgnpy import Bbox, Map, MapLanguage, Sbgn map = Map( id="ethanol", language=MapLanguage.PROCESS_DESCRIPTION, bbox=Bbox(x=0, y=0, w=363, h=253), ) sbgn = Sbgn(map=[map]) ``` `MapLanguage` is the language of the map, i.e., which vocabulary of glyphs and arcs applies: | language | what it describes | | --- | --- | | `MapLanguage.PROCESS_DESCRIPTION` | what is converted into what | | `MapLanguage.ENTITY_RELATIONSHIP` | which entity influences which other entity | | `MapLanguage.ACTIVITY_FLOW` | the flow of activity between the entities | All classes are keyword only, i.e., `Map(id="ethanol")` works and `Map("ethanol")` does not. Every list attribute (`map`, `glyph`, `arc`, `port`) defaults to an empty list, so it can be extended after the object was created. ## Glyphs A `Glyph` is a node of the map. It carries its class, an id which the arcs refer to, an optional `Label` and the `Bbox` it is drawn in: ```python from libsbgnpy import Bbox, Glyph, GlyphClass, Label map.glyph.append( Glyph( id="ethanol", class_value=GlyphClass.SIMPLE_CHEMICAL, label=Label(text="Ethanol"), bbox=Bbox(x=40, y=120, w=60, h=60), ) ) ``` The class of a glyph is the shape it is drawn as, `GlyphClass` has all classes of the three languages, e.g., `SIMPLE_CHEMICAL`, `MACROMOLECULE`, `COMPLEX`, `PROCESS`, `COMPARTMENT`, `BIOLOGICAL_ACTIVITY` or `PHENOTYPE`. The attribute is called `class_value`, since `class` is a python keyword. !!! note "Coordinates" A bounding box is given in the coordinate system of the map: `x` and `y` are the top left corner, `w` and `h` the width and the height. There is no unit, the numbers are pixels of the drawing, and `y` grows downwards. Glyphs are nested: a compartment or a complex holds the glyphs inside it in its own `glyph` list, and the auxiliary units of an entity, e.g., a state variable or a unit of information, are glyphs as well: ```python complex = Glyph( id="complex1", class_value=GlyphClass.COMPLEX, bbox=Bbox(x=20, y=20, w=200, h=120), ) complex.glyph.append( Glyph( id="subunit1", class_value=GlyphClass.MACROMOLECULE, label=Label(text="ADH1"), bbox=Bbox(x=40, y=40, w=80, h=40), ) ) ``` A `Glyph.Clone` marks an entity which is drawn more than once in the same map, so that a reader knows the two circles are the same molecule: ```python Glyph( id="nad", class_value=GlyphClass.SIMPLE_CHEMICAL, label=Label(text="NAD+"), bbox=Bbox(x=40, y=190, w=60, h=60), clone=Glyph.Clone(), ) ``` ## Ports and arcs An `Arc` connects a source with a target, both given as the id of a glyph or of a port. Its class states what the connection means, and `Arc.Start` and `Arc.End` are the points it is drawn between: ```python from libsbgnpy import Arc, ArcClass map.arc.append( Arc( id="a01", class_value=ArcClass.CONSUMPTION, source="ethanol", target="pn1.1", start=Arc.Start(x=98, y=160), end=Arc.End(x=136, y=180), ) ) ``` A process glyph is not connected directly: it carries `Port` objects, one for each side, and the arcs attach to those. This is what makes the direction of a reaction unambiguous: ```python from libsbgnpy import GlyphOrientation, Port Glyph( id="pn1", class_value=GlyphClass.PROCESS, orientation=GlyphOrientation.HORIZONTAL, bbox=Bbox(x=148, y=168, w=24, h=24), port=[ Port(id="pn1.1", x=136, y=180), Port(id="pn1.2", x=184, y=180), ], ) ``` `ArcClass` covers the arcs of the three languages, e.g., `CONSUMPTION` and `PRODUCTION` for the substrates and products of a process, `CATALYSIS`, `INHIBITION`, `STIMULATION` and `MODULATION` for the influences on it, and `LOGIC_ARC` for the input of a logical operator. ## Labels A `Label` is the text drawn on a glyph. The text is unicode, so greek letters, primes and the characters which have to be escaped in XML are written as they are: ```python Glyph( id="glyph1", class_value=GlyphClass.MACROMOLECULE, label=Label(text="α/β hydrolase"), bbox=Bbox(x=5, y=70, w=160, h=60), ) ``` The serializer escapes what has to be escaped, see [Reading and writing](io.md), and the round trip returns the text unchanged. Line breaks in a label are allowed, they are drawn as line breaks. ## Notes and extensions Every element of a map, from the document down to a single port, inherits from `Sbgnbase` and can therefore carry `notes` with a human readable description and an `extension` with additional machine readable content, see [Notes and extensions](extensions.md). ## Examples The runnable examples are in [`examples/`](https://github.com/matthiaskoenig/libsbgnpy/tree/develop/examples): | example | what it shows | | --- | --- | | [`write.py`](https://github.com/matthiaskoenig/libsbgnpy/blob/develop/examples/write.py) | create SBGN documents from scratch | | [`clone_marker.py`](https://github.com/matthiaskoenig/libsbgnpy/blob/develop/examples/clone_marker.py) | mark glyphs which occur more than once | | [`labels.py`](https://github.com/matthiaskoenig/libsbgnpy/blob/develop/examples/labels.py) | special characters in labels | --- # Reading and writing SBGN-ML documents are read and written with the functions of `libsbgnpy.io`, which use [xsdata](https://github.com/tefra/xsdata) to map the XML onto the classes of [`libsbgnpy.sbgn`](maps.md). ## Reading `read_sbgn_from_file` reads a document from a path, `read_sbgn_from_string` from a string: ```python from pathlib import Path from libsbgnpy import read_sbgn_from_file sbgn = read_sbgn_from_file(Path("examples/sbgn/adh.sbgn")) map = sbgn.map[0] for glyph in map.glyph: print(glyph.id, glyph.class_value) for arc in map.arc: print(arc.id, arc.class_value, arc.source, "->", arc.target) ``` Reading does not validate the document against the schema, it only parses it. To check that a document follows the schema see [Validation](validation.md). A file which is no well formed XML raises an `xsdata.exceptions.ParserError`, and the path of the file is logged: ```python from xsdata.exceptions import ParserError try: sbgn = read_sbgn_from_file(Path("broken.sbgn")) except ParserError as err: print(f"not an SBGN document: {err}") ``` ### Older SBGN-ML versions The bindings are generated from the SBGN-ML 0.3 schema. Documents in the earlier namespaces `http://sbgn.org/libsbgn/0.1` and `http://sbgn.org/libsbgn/0.2` are upconverted while reading, i.e., they are read like a 0.3 document and written back as one. The conversion is `upconvert`, which is applied by the reader and by the validator: ```python from libsbgnpy.io import upconvert upconvert('') # '' ``` ## Writing `write_sbgn_to_file` writes a document to a path, `write_sbgn_to_string` returns it as a string: ```python from pathlib import Path from libsbgnpy import write_sbgn_to_file, write_sbgn_to_string write_sbgn_to_file(sbgn, Path("map.sbgn")) xml_str = write_sbgn_to_string(sbgn) ``` The document is written in the SBGN-ML 0.3 namespace, indented with two spaces, with an XML declaration and in UTF-8. Characters which have to be escaped in XML are escaped, so a label like `a < b` survives the round trip: ```python from libsbgnpy import read_sbgn_from_string, write_sbgn_to_string sbgn2 = read_sbgn_from_string(write_sbgn_to_string(sbgn)) ``` The raw XML of the `notes` and `extension` elements is written as markup rather than as escaped text, see [Notes and extensions](extensions.md). ## Examples | example | what it shows | | --- | --- | | [`read.py`](https://github.com/matthiaskoenig/libsbgnpy/blob/develop/examples/read.py) | read a document and display its content | | [`write.py`](https://github.com/matthiaskoenig/libsbgnpy/blob/develop/examples/write.py) | create documents from scratch and write them | --- # Notes and extensions Every SBGN element inherits from `Sbgnbase` and therefore carries two containers for content the schema does not describe itself: - **`notes`** — a human readable description of the element, as XHTML, see the [SBGN-ML notes](https://github.com/sbgn/sbgn/wiki/SBGN-ML_Notes), - **`extension`** — machine readable content, as arbitrary XML, see the [SBGN-ML extensions](https://github.com/sbgn/sbgn/wiki/SBGN-ML_Extensions). Both hold XML which the bindings do not interpret. It is **set as a string** and **read back as an element tree**, so the two directions are not symmetric. ## Writing The content is set as a string, one entry per element: ```python from libsbgnpy import ( Bbox, Glyph, GlyphClass, Label, Map, MapLanguage, Sbgn, Sbgnbase, ) glyph = Glyph( id="g1", class_value=GlyphClass.MACROMOLECULE, label=Label(text="INSR"), bbox=Bbox(x=100, y=100, w=80, h=40), notes=Sbgnbase.Notes( w3_org_1999_xhtml_element=[ 'The insulin receptor.', ] ), ) ``` An extension is set the same way, on any element: ```python map = Map(id="m", language=MapLanguage.PROCESS_DESCRIPTION) map.extension = Sbgnbase.Extension( any_element=[ '' "..." "", ] ) ``` The strings are parsed and written as markup when the document is serialized, so the result is well formed XML and more than one entry can be stored. The document which is passed in is not modified. !!! warning "The XML has to be well formed" A string which is not well formed XML raises an `lxml.etree.XMLSyntaxError` when the document is written. Every entry is a single element, with its namespace declared on it. ## Reading Reading a document turns each entry into an `AnyElement` tree, since the bindings do not know the schema of the content. `element_to_string` serializes such an entry back to XML: ```python from pathlib import Path from libsbgnpy import element_to_string, read_sbgn_from_file sbgn = read_sbgn_from_file(Path("map.sbgn")) for glyph in sbgn.map[0].glyph: if glyph.notes is None: continue for element in glyph.notes.w3_org_1999_xhtml_element: print(element_to_string(element)) # The insulin receptor. ``` The counterpart is `element_from_string`, which parses XML into such an entry. It is applied automatically when a document is written, so it is only needed to work with the entries directly. Render information stored in an extension is read back with `read_render_from_extension`, see [Render information](render.md). ## Examples | example | what it shows | | --- | --- | | [`notes.py`](https://github.com/matthiaskoenig/libsbgnpy/blob/develop/examples/notes.py) | write and read notes | | [`extension.py`](https://github.com/matthiaskoenig/libsbgnpy/blob/develop/examples/extension.py) | write and read extensions | --- # Render information SBGN says what a map means, not what it looks like. Colors, line widths and fonts are therefore not part of the SBGN-ML schema: they are stored as an extension of the map, using the vocabulary of the [SBML render extension](https://sbml.org/documents/specifications/level-3/version-1/render/), see the [SBGN-ML rendering](https://github.com/sbgn/sbgn/wiki/SBGN-ML_Rendering). `libsbgnpy.render` holds the python bindings of that vocabulary. ## The structure A `RenderInformation` object has three parts: - **`ListOfColorDefinitions`** — named colors, i.e., a `ColorDefinition` maps an id to a hex value such as `#ff9900` or `#1f77b4bb` with an alpha channel, - **`ListOfGradientDefinitions`** — named gradients, i.e., a `LinearGradient` with its stops, - **`ListOfStyles`** — a `Style` applies a `G` to the glyphs and arcs it lists in `id_list`; the `G` carries `stroke`, `stroke_width`, `fill` and the font attributes, and refers to the colors and gradients by their id. ```python from libsbgnpy import ( ColorDefinition, G, ListOfColorDefinitions, ListOfGradientDefinitions, ListOfStyles, RenderInformation, Style, ) render_info = RenderInformation( id="example", program_name="libsbgnpy", program_version="1.0.0", list_of_color_definitions=ListOfColorDefinitions( color_definition=[ ColorDefinition(id="grey", value="#969696"), ColorDefinition(id="orange", value="#ff9900"), ] ), list_of_gradient_definitions=ListOfGradientDefinitions(), list_of_styles=ListOfStyles( style=[ Style( id_list="glyph1 glyph2", g=G(stroke="grey", stroke_width=5, fill="orange"), ), ] ), ) ``` `id_list` is a space separated list of the ids of the glyphs and arcs the style applies to. ## Storing it in a map The render information is serialized and stored as an extension of the map: ```python from libsbgnpy import Sbgnbase, write_render_to_string map.extension = Sbgnbase.Extension(any_element=[write_render_to_string(render_info)]) ``` `write_render_to_string` writes the document without an XML declaration and without a namespace prefix, which is what belongs into an extension. ## Reading it back `read_render_from_extension` finds the `renderInformation` in an extension and parses it: ```python from pathlib import Path from libsbgnpy import read_render_from_extension, read_sbgn_from_file sbgn = read_sbgn_from_file(Path("map.sbgn")) render_info = read_render_from_extension(sbgn.map[0].extension) if render_info is not None: for color in render_info.list_of_color_definitions.color_definition: print(color.id, color.value) # grey #969696 ``` It returns `None` if the map carries no render information. To parse a `renderInformation` document which is not stored in an extension use `read_render_from_string`. ## Examples | example | what it shows | | --- | --- | | [`render.py`](https://github.com/matthiaskoenig/libsbgnpy/blob/develop/examples/render.py) | write and read render information | | [`ethanol.py`](https://github.com/matthiaskoenig/libsbgnpy/blob/develop/examples/ethanol.py) | color a map and render it as an image | --- # Validation Reading a document parses it, it does not check that it follows the SBGN-ML schema. `validate_xsd` performs that check against the packaged schema, `libsbgnpy/schema/SBGN.xsd`: ```python from pathlib import Path from libsbgnpy import validate_xsd errors = validate_xsd(Path("map.sbgn")) if errors: for error in errors: print(error) else: print("valid") ``` The function returns the errors as a list of strings, which is empty for a valid document, so a check is `if validate_xsd(f):`. Nothing is written to stdout or stderr; a summary is logged at info level, see [Logging](installation.md#logging). An error names the line, the element and what is wrong with it: ``` :3:0:ERROR:SCHEMASV:SCHEMAV_CVC_COMPLEX_TYPE_4: Element '{http://sbgn.org/libsbgn/0.3}map': The attribute 'id' is required but missing. ``` A file which is no well formed XML is reported as a single error rather than raising, so a corrupt file and an invalid one are handled the same way. ## An invalid document [`examples/sbgn/invalid.sbgn`](https://github.com/matthiaskoenig/libsbgnpy/blob/develop/examples/sbgn/invalid.sbgn) breaks the schema in three ways, one per kind of error the validation reports: ```xml ... ``` `validate_xsd` returns the three errors: ```python from pathlib import Path from libsbgnpy import validate_xsd for error in validate_xsd(Path("examples/sbgn/invalid.sbgn")): print(error) ``` - a required attribute is missing, the `id` of the map: ``` SCHEMAV_CVC_COMPLEX_TYPE_4: Element '{http://sbgn.org/libsbgn/0.3}map': The attribute 'id' is required but missing. ``` - an attribute carries a value which is not in the enumeration, `simple chemcial` is not an SBGN glyph class. The error lists every class the schema allows, which is the fastest way to find the correct spelling: ``` SCHEMAV_CVC_ENUMERATION_VALID: Element '{http://sbgn.org/libsbgn/0.3}glyph', attribute 'class': [facet 'enumeration'] The value 'simple chemcial' is not an element of the set {'unspecified entity', 'simple chemical', ...}. ``` - a required child element is missing, an arc needs an `end` point: ``` SCHEMAV_ELEMENT_CONTENT: Element '{http://sbgn.org/libsbgn/0.3}arc': Missing child element(s). Expected is one of ( {http://sbgn.org/libsbgn/0.3}next, {http://sbgn.org/libsbgn/0.3}end ). ``` The line number in front of every error, e.g. `:13:0:`, is the line of the *upconverted* document, which is what the validation sees, see [Which schema is used](#which-schema-is-used). For a 0.3 document it is the line of the file; for a 0.1 or 0.2 document only the namespace of the root element changes, so the lines still match. !!! note A document which does not validate can still be read. `read_sbgn_from_file` parses without validating, so the invalid classes end up in the bindings as they are written. Validate first if a document comes from somewhere else. ## Which schema is used The packaged schema is the SBGN-ML 0.3 schema. Documents in the earlier namespaces are upconverted before they are validated, exactly as they are when they are read, see [Older SBGN-ML versions](io.md#older-sbgn-ml-versions). A 0.1 or 0.2 document is therefore validated against the 0.3 schema, which is what `libsbgnpy` reads it as. ## What is not checked An XSD schema checks the structure of a document: which elements may occur where, which attributes are required, and which values an enumeration allows. It does not check the rules of the SBGN languages, e.g., that a consumption arc starts at an entity pool node and ends at a process, or that a process has at most one arc per port. Those rules are the validation rules of the SBGN specifications; they are not implemented, see [issue #60](https://github.com/matthiaskoenig/libsbgnpy/issues/60). ## Examples | example | what it shows | | --- | --- | | [`validate.py`](https://github.com/matthiaskoenig/libsbgnpy/blob/develop/examples/validate.py) | validate the documents in `examples/sbgn/` and report the errors | ```bash python examples/validate.py ``` ``` valid: adh.sbgn valid: adh_0.3.sbgn valid: glycolysis.sbgn invalid: invalid.sbgn :11:0:ERROR:SCHEMASV:SCHEMAV_CVC_COMPLEX_TYPE_4: ... :13:0:ERROR:SCHEMASV:SCHEMAV_CVC_ENUMERATION_VALID: ... :22:0:ERROR:SCHEMASV:SCHEMAV_ELEMENT_CONTENT: ... valid: neuronal_muscle_signalling_color.sbgn 4/5 documents are valid invalid: invalid.sbgn ``` --- # Images `libsbgnpy` does not draw maps itself. `render_sbgn` sends a document to the rendering web service of Frank Bergmann at , which lays it out and returns the image: ```python from pathlib import Path from libsbgnpy import read_sbgn_from_file, render_sbgn sbgn = read_sbgn_from_file(Path("examples/sbgn/adh.sbgn")) render_sbgn(sbgn, Path("adh.png")) ``` ![ethanol example](images/ethanol_example.png) The request is equivalent to ```bash curl -X POST -F file=@"map.sbgn" https://sbml.bioquant.uni-heidelberg.de/layout -o map.png ``` ## Requirements and errors Rendering needs an internet connection. The service is queried with a timeout of 60 seconds, `requests` raises a `RequestException` if it cannot be reached or answers with an error. Only PNG is supported, and the image file has to end in `.png`; anything else raises a `ValueError` before the request is made. ## The layout comes from the document The service draws the map at the coordinates the document carries, i.e., the `Bbox` of every glyph and the `start`, `end` and `next` points of every arc. It does not compute a layout, so a document without coordinates renders as an empty or a collapsed image. See [SBGN maps](maps.md#ports-and-arcs) for how the coordinates are set. ## Examples | example | what it shows | | --- | --- | | [`ethanol.py`](https://github.com/matthiaskoenig/libsbgnpy/blob/develop/examples/ethanol.py) | build a map step by step and render it after every step | --- # API reference The API reference is generated from the docstrings of the package. ## The SBGN bindings The python bindings of the SBGN-ML schemas, generated with [xsdata](https://github.com/tefra/xsdata), see [SBGN maps](../maps.md). The classes mirror the schemas, so the SBGN specifications are the reference for what an element means. | module | description | | --- | --- | | [sbgn](sbgn.md) | maps, glyphs, arcs and their classes | | [render](render.md) | colors, gradients and styles of a map | ## Working with SBGN documents | module | description | | --- | --- | | [io](io.md) | reading and writing of SBGN documents | | [validator](validator.md) | validation against the SBGN XSD schema | | [image](image.md) | rendering of a map as an image | ## Output of the package | module | description | | --- | --- | | [console](console.md) | shared rich console for scripts and examples | | [log](log.md) | logging of the package | --- # libsbgnpy.sbgn Python bindings of the SBGN-ML schema. Generated from `libsbgnpy/schema/SBGN.xsd` with [xsdata](https://github.com/tefra/xsdata), see `libsbgnpy/schema/README.md`; do not edit by hand. The classes mirror the schema, so the [SBGN specifications](https://github.com/sbgn/sbgn/wiki/SBGN_Specifications) are the reference for what an element means, and the docstrings are the documentation of the schema. An SBGN document is an `Sbgn` holding `Map` objects, a map holds `Glyph` and `Arc` objects, and every element inherits from `Sbgnbase`, which carries the `notes` and the `extension`. See the user guide for how they are used. ## class `Arc(*, notes: 'Sbgnbase.Notes | None' = None, extension: 'Sbgnbase.Extension | None' = None, glyph: 'list[Glyph]' = , port: 'list[Port]' = , start: 'Arc.Start', next: 'list[Arc.Next]' = , end: 'Arc.End', class_value: 'ArcClass', id: 'str', source: 'str', target: 'str') -> None` The arc element describes an SBGN arc between two SBGN nodes. It contains: For PD: an optional stoichiometry marker, For ER: an optional cardinality marker, zero or more ports (influence targets), and zero or more outcomes, a mandatory source and target (glyph or port), a geometric description of its whole path, from start to end. This path can involve any number of straight lines or quadratic/cubic Bezier curves. . :ivar glyph: In PD, an arc can contain a single optional sub-glyph. This glyph must be a stoichiometry marker (square with a numeric label) In ER, an arc can contain several sub-glyphs. This can be zero or one cardinality glyphs (e.g. cis or trans), plus zero to many outcome glyphs (black dot) :ivar port: Ports are only allowed in ER. :ivar start: The start element represents the starting point of the arc's path. It is unique and mandatory. :ivar next: The next element represents the next point in the arc's path. Between the start and the end of the path, there can be any number (even zero) of next elements (intermediate points). They are read consecutively: start, next, next, ..., next, end. When the path from the previous point to this point is not straight, this element also contains a list of control points (between 1 and 2) describing a Bezier curve (quadratic if 1 control point, cubic if 2) between the previous point and this point. :ivar end: The end element represents the ending point of the arc's path. It is unique and mandatory. When the path from the previous point to this point is not straight, this element also contains a list of control points (between 1 and 2) describing a Bezier curve (quadratic if 1 control point, cubic if 2) between the previous point and this point. :ivar class_value: The class attribute defines the semantic of the arc, and influences: the way that arc should be rendered, the overall syntactic validity of the map. The various classes encompass all possible types of SBGN arcs: production and consumption arcs, all types of modification arcs, logic arcs, equivalence arcs. To express a reversible reaction, use production arcs on both sides of the Process Node. :ivar id: The xsd:ID type is an alphanumeric identifier, starting with a letter. :ivar source: The source attribute can refer: either to the id of a glyph, or to the id of a port on a glyph. :ivar target: The target attribute can refer: either to the id of a glyph, or to the id of a port on a glyph. ### `Arc.End(*, point: 'list[Point]' = , x: 'float', y: 'float') -> None` End(*, point: 'list[Point]' = , x: 'float', y: 'float') ### `Arc.Extension(*, any_element: 'list[object]' = ) -> None` The extension element stores extension information like render information, metadata or annotations. . ### `Arc.Meta()` ### `Arc.Next(*, point: 'list[Point]' = , x: 'float', y: 'float') -> None` Next(*, point: 'list[Point]' = , x: 'float', y: 'float') ### `Arc.Notes(*, w3_org_1999_xhtml_element: 'list[object]' = ) -> None` The notes element stores notes. . ### `Arc.Start(*, x: 'float', y: 'float') -> None` Start(*, x: 'float', y: 'float') ## class `ArcClass(*values)` ## class `Arcgroup(*, notes: 'Sbgnbase.Notes | None' = None, extension: 'Sbgnbase.Extension | None' = None, glyph: 'list[Glyph]' = , arc: 'list[Arc]' = , class_value: 'ArcgroupClass') -> None` The arc group describes a set of arcs and glyphs that together have a relation. For example For ER: interaction arcs around an interaction glyph, ... Note that, in spite of the name, an arcgroup contains both arcs and glyphs. . :ivar glyph: An arcgroup can contain glyphs. For example, in an interaction arcgroup, there must be one interaction glyph. :ivar arc: An arcgroup can have multiple arcs. They are all assumed to form a single hyperarc-like structure. :ivar class_value: The class attribute defines the semantic of the arcgroup. ### `Arcgroup.Extension(*, any_element: 'list[object]' = ) -> None` The extension element stores extension information like render information, metadata or annotations. . ### `Arcgroup.Meta()` ### `Arcgroup.Notes(*, w3_org_1999_xhtml_element: 'list[object]' = ) -> None` The notes element stores notes. . ## class `ArcgroupClass(*values)` ## class `Bbox(*, notes: 'Sbgnbase.Notes | None' = None, extension: 'Sbgnbase.Extension | None' = None, x: 'float', y: 'float', w: 'float', h: 'float') -> None` The bbox element describes a rectangle. This rectangle is defined by: PointAttributes corresponding to the 2D coordinates of the top left corner, width and height attributes. The rectangle corresponds to the outer bounding box of a shape. The shape itself can be irregular (for instance in the case of some compartments). In the case of process nodes, the bounding box only concerns the central glyph (square, or circle), the input/output ports are not included, and neither are the lines connecting them to the central glyph. A bbox is required for all glyphs, and is optional for labels. . ### `Bbox.Extension(*, any_element: 'list[object]' = ) -> None` The extension element stores extension information like render information, metadata or annotations. . ### `Bbox.Meta()` ### `Bbox.Notes(*, w3_org_1999_xhtml_element: 'list[object]' = ) -> None` The notes element stores notes. . ## class `EntityName(*values)` ## class `Glyph(*, notes: 'Sbgnbase.Notes | None' = None, extension: 'Sbgnbase.Extension | None' = None, label: 'Label | None' = None, state: 'Glyph.State | None' = None, clone: 'Glyph.Clone | None' = None, callout: 'Glyph.Callout | None' = None, entity: 'Glyph.Entity | None' = None, bbox: 'Bbox', glyph: 'list[Glyph]' = , port: 'list[Port]' = , class_value: 'GlyphClass', orientation: 'GlyphOrientation' = , id: 'str', compartment_ref: 'str | None' = None, compartment_order: 'float | None' = None, map_ref: 'str | None' = None, tag_ref: 'str | None' = None) -> None` The glyph element is: either a stand-alone, high-level SBGN glyph (EPN, PN, compartment, etc), or a sub-glyph (state variable, unit of information, inside of a complex, ...) In the first case, it appears directly in the glyph list of the map. In the second case, it is a child of another glyph element. . :ivar label: :ivar state: The state element should only be used for state variables. It replaces the label element used for other glyphs. It describes the text to be drawn inside the state variable. A state must have a value, a variable, or both. If it has both, they are rendered as a concatenated string with @ in between. :ivar clone: The clone element (which is optional) means the glyph carries a clone marker. It can contain an optional label. :ivar callout: The callout element is only used for glyphs with class annotation. It contains the coordinate of the point where the annotation points to, as well as a reference to the element that is pointed to. :ivar entity: The entity is only used in activity flow diagrams. It can only be used on a unit of information glyph on a biological activity glyph, where it is compulsory. It is used to indicate the shape of this unit of information. :ivar bbox: The bbox element is mandatory and unique: exactly one per glyph. It defines the outer bounding box of the glyph. The actual shape of the glyph can be irregular (for instance in the case of some compartments) In the case of process nodes, the bounding box only concerns the central glyph (square, or circle): the input/output ports are not included, and neither are the lines connecting them to the central glyph. :ivar glyph: A glyph element can contain any number of children glyph elements. In practice, this should only happen in the following cases: a compartment with unit of information children, an EPN with states variables and/or unit of information children, a complex, with state variables, unit of info, and/or EPN children. :ivar port: :ivar class_value: The class attribute defines the semantic of the glyph, and influences: the way that glyph should be rendered, the overall syntactic validity of the map. The various classes encompass the following PD SBGN elements: Entity Pool Nodes (EPN), Process Nodes (PN), Logic Operator Nodes, Sub-glyphs on Nodes (State Variable, Unit of Information), Sub-glyphs on Arcs (Stoichiometry Label), Other glyphs (Compartment, Submap, Tag, Terminal). And the following ER SBGN elements Entities (Entity, Outcome) Other (Annotation, Phenotype) Auxiliary on glyps (Existence, Location) Auxiliary on arcs (Cardinality) Delay operator implicit xor :ivar orientation: The orientation attribute is used to express how to draw asymmetric glyphs. In PD, the orientation of Process Nodes is either horizontal or vertical. It refers to an (imaginary) line connecting the two in/out sides of the PN. In PD, the orientation of Tags and Terminals can be left, right, up or down. It refers to the direction the arrow side of the glyph is pointing at. :ivar id: The xsd:ID type is an alphanumeric identifier, starting with a letter. It is recommended to generate meaningless IDs (e.g. "glyph1234") and avoid IDs with a meaning (e.g. "epn_ethanol") :ivar compartment_ref: Reference to the ID of the compartment that this glyph is part of. Only use this if there is at least one explicit compartment present in the diagram. Compartments are only used in PD and AF, and thus this attribute as well. For PD, this should be used only for EPN's. For AF, this should be used only for Activity Nodes. In case there are no compartments, entities that can have a location, such as EPN's, are implicit member of an invisible compartment that encompasses the whole map. In that case, this attribute must be omitted. :ivar compartment_order: The compartment order attribute can be used to define a drawing order for compartments. It enables tools to draw compartments in the correct order especially in the case of overlapping compartments. Compartments are only used in PD and AF, and thus this attribute as well. The attribute is of type float, the attribute value has not to be unique. Compartments with higher compartment order are drawn on top. The attribute is optional and should only be used for compartments. :ivar map_ref: This attribute is only used on a submap glyph. It is required. Reference to the ID of the map which provides the content of the submap. If no map is available providing the content of the submap an omitted process should be used instead of the submap. Submaps are only used in PD and AF, and thus this attribute as well. :ivar tag_ref: This attribute is only used on a terminal glyph. It is required. Reference to the ID of a tag on a map providing the content of a submap. The terminal glyph is defined as sub-glyph of this submap. Submaps and therefore terminals are only used in PD and AF, and thus this attribute as well. ### `Glyph.Callout(*, point: 'Point', target: 'str | None' = None) -> None` Callout(*, point: 'Point', target: 'str | None' = None) ### `Glyph.Clone(*, label: 'Label | None' = None) -> None` Clone(*, label: 'Label | None' = None) ### `Glyph.Entity(*, name: 'EntityName') -> None` Entity(*, name: 'EntityName') ### `Glyph.Extension(*, any_element: 'list[object]' = ) -> None` The extension element stores extension information like render information, metadata or annotations. . ### `Glyph.Meta()` ### `Glyph.Notes(*, w3_org_1999_xhtml_element: 'list[object]' = ) -> None` The notes element stores notes. . ### `Glyph.State(*, value: 'str | None' = None, variable: 'str | None' = None) -> None` :ivar value: The value attribute represents the state of the variable. It can be: either from a predefined set of string (P, S, etc.) which correspond to specific SBO terms (cf. SBGN specs), or any arbitrary string. :ivar variable: The variable attribute describes the site where the modification described by the value attribute occurs. It is: optional when there is only one state variable on the parent EPN, required when there is more than one state variable the parent EPN. ## class `GlyphClass(*values)` ## class `GlyphOrientation(*values)` ## class `Label(*, notes: 'Sbgnbase.Notes | None' = None, extension: 'Sbgnbase.Extension | None' = None, bbox: 'Bbox | None' = None, text: 'str') -> None` The label element describes the text accompanying a glyph. The text attribute is mandatory. Its position can be specified by a bbox (optional). Tools are free to display the text in any style (font, font-size, etc.) . :ivar bbox: :ivar text: Multi- line labels are allowed. Line breaks are encoded as &#xA; as specified by the XML standard. ### `Label.Extension(*, any_element: 'list[object]' = ) -> None` The extension element stores extension information like render information, metadata or annotations. . ### `Label.Meta()` ### `Label.Notes(*, w3_org_1999_xhtml_element: 'list[object]' = ) -> None` The notes element stores notes. . ## class `Map(*, notes: 'Sbgnbase.Notes | None' = None, extension: 'Sbgnbase.Extension | None' = None, bbox: 'Bbox | None' = None, glyph: 'list[Glyph]' = , arc: 'list[Arc]' = , arcgroup: 'list[Arcgroup]' = , version: 'MapVersion | None' = None, language: 'MapLanguage | None' = None, id: 'str') -> None` The map element describes a single SBGN PD map. It contains a list of glyph elements and a list of arc elements. These lists can be of any size (possibly empty). . :ivar bbox: The bbox element on a map is not mandatory, it allows the application to define a canvas, and at the same time define a whitespace margin around the glyphs. If a bbox is defined on a map, all glyphs and arcs must be inside this bbox, otherwise they could be clipped off by applications. :ivar glyph: :ivar arc: :ivar arcgroup: :ivar version: Version of the map: URI identifier that gives the language, level and version defined by SBGN. Different languages/levels/versions have different restrictions on the usage of sub-elements (that are not encoded in this schema but must be validated with an external validator) :ivar language: Language of the map: one of three sublanguages defined by SBGN. Different languages have different restrictions on the usage of sub-elements (that are not encoded in this schema but must be validated with an external validator) :ivar id: The xsd:ID type is an alphanumeric identifier, starting with a letter. It is recommended to generate meaningless IDs (e.g. "map1234") and avoid IDs with a meaning (e.g. "MAPK cascade") ### `Map.Extension(*, any_element: 'list[object]' = ) -> None` The extension element stores extension information like render information, metadata or annotations. . ### `Map.Meta()` ### `Map.Notes(*, w3_org_1999_xhtml_element: 'list[object]' = ) -> None` The notes element stores notes. . ## class `MapLanguage(*values)` ## class `MapVersion(*values)` ## class `Point(*, notes: 'Sbgnbase.Notes | None' = None, extension: 'Sbgnbase.Extension | None' = None, x: 'float', y: 'float') -> None` The point element is characterized by PointAttributes, which describe absolute 2D cartesian coordinates. Namely: x (horizontal, from left to right), y (vertical, from top to bottom). The origin is located in the top-left corner of the map. There is no unit: proportions must be preserved, but the maps can be drawn at any scale. In the test files examples, to obtain a drawing similar to the reference *.png file, values in the corresponding *.sbgn file should be read as pixels. . ### `Point.Extension(*, any_element: 'list[object]' = ) -> None` The extension element stores extension information like render information, metadata or annotations. . ### `Point.Meta()` ### `Point.Notes(*, w3_org_1999_xhtml_element: 'list[object]' = ) -> None` The notes element stores notes. . ## class `Port(*, notes: 'Sbgnbase.Notes | None' = None, extension: 'Sbgnbase.Extension | None' = None, x: 'float', y: 'float', id: 'str') -> None` The port element describes an anchor point to which arcs can refer as a source or target. It consists of: absolute 2D cartesian coordinates (PointAttribute), a unique id attribute. Two port elements are required for process nodes. They represent the extremity of the two "arms" which protrude on both sides of the core of the glyph (= square or circle shape). Other glyphs don't need ports (but can use them if desired). . :ivar x: :ivar y: :ivar id: The xsd:ID type is an alphanumeric identifier, starting with a letter. Port IDs often contain the ID of their glyph, followed by a local port number (e.g. glyph4.1, glyph4.2, etc.) However, this style convention is not mandatory, and IDs should never be interpreted as carrying any meaning. ### `Port.Extension(*, any_element: 'list[object]' = ) -> None` The extension element stores extension information like render information, metadata or annotations. . ### `Port.Meta()` ### `Port.Notes(*, w3_org_1999_xhtml_element: 'list[object]' = ) -> None` The notes element stores notes. . ## class `Sbgn(*, notes: 'Sbgnbase.Notes | None' = None, extension: 'Sbgnbase.Extension | None' = None, map: 'list[Map]' = ) -> None` The sbgn element is the root of any SBGNML document. Currently each document must contain exactly one map element. . ### `Sbgn.Extension(*, any_element: 'list[object]' = ) -> None` The extension element stores extension information like render information, metadata or annotations. . ### `Sbgn.Meta()` ### `Sbgn.Notes(*, w3_org_1999_xhtml_element: 'list[object]' = ) -> None` The notes element stores notes. . ## class `Sbgnbase(*, notes: 'Sbgnbase.Notes | None' = None, extension: 'Sbgnbase.Extension | None' = None) -> None` The SBGNBase type is the base type of all main components in SBGN. It supports attaching notes and extensions to components, with metadata and annotations encoded in the extension element. . ### `Sbgnbase.Extension(*, any_element: 'list[object]' = ) -> None` The extension element stores extension information like render information, metadata or annotations. . ### `Sbgnbase.Meta()` ### `Sbgnbase.Notes(*, w3_org_1999_xhtml_element: 'list[object]' = ) -> None` The notes element stores notes. . --- # libsbgnpy.render Python bindings of the render extension used by SBGN-ML. Generated from `libsbgnpy/schema/render.xsd` with [xsdata](https://github.com/tefra/xsdata), see `libsbgnpy/schema/README.md`; do not edit by hand. Render information colours a map. It is not part of the SBGN-ML schema: a `RenderInformation` document is stored as an extension of the map, using the vocabulary of the [SBML render extension](https://sbml.org/documents/specifications/level-3/version-1/render/). ## class `ColorDefinition(*, id: 'str', value: 'str') -> None` ColorDefinition(*, id: 'str', value: 'str') ### `ColorDefinition.Meta()` ## class `G(*, stroke: 'str | None' = None, stroke_width: 'float | None' = None, fill: 'str | None' = None, fill_rule: 'str | None' = None, font_family: 'str | None' = None, font_weight: 'str | None' = None, font_style: 'str | None' = None, text_anchor: 'str | None' = None, vtext_anchor: 'str | None' = None, font_size: 'int | None' = None) -> None` G(*, stroke: 'str | None' = None, stroke_width: 'float | None' = None, fill: 'str | None' = None, fill_rule: 'str | None' = None, font_family: 'str | None' = None, font_weight: 'str | None' = None, font_style: 'str | None' = None, text_anchor: 'str | None' = None, vtext_anchor: 'str | None' = None, font_size: 'int | None' = None) ### `G.Meta()` ## class `LinearGradient(*, stop: 'list[LinearGradient.Stop]' = , id: 'str', x1: 'str | None' = None, x2: 'str | None' = None, y1: 'str | None' = None, y2: 'str | None' = None) -> None` LinearGradient(*, stop: 'list[LinearGradient.Stop]' = , id: 'str', x1: 'str | None' = None, x2: 'str | None' = None, y1: 'str | None' = None, y2: 'str | None' = None) ### `LinearGradient.Meta()` ### `LinearGradient.Stop(*, offset: 'str', stop_color: 'str') -> None` Stop(*, offset: 'str', stop_color: 'str') ## class `ListOfColorDefinitions(*, color_definition: 'list[ColorDefinition]' = ) -> None` ListOfColorDefinitions(*, color_definition: 'list[ColorDefinition]' = ) ### `ListOfColorDefinitions.Meta()` ## class `ListOfGradientDefinitions(*, linear_gradient: 'list[LinearGradient]' = ) -> None` ListOfGradientDefinitions(*, linear_gradient: 'list[LinearGradient]' = ) ### `ListOfGradientDefinitions.Meta()` ## class `ListOfStyles(*, style: 'list[Style]' = ) -> None` ListOfStyles(*, style: 'list[Style]' = ) ### `ListOfStyles.Meta()` ## class `RenderInformation(*, list_of_color_definitions: 'ListOfColorDefinitions', list_of_gradient_definitions: 'ListOfGradientDefinitions', list_of_styles: 'ListOfStyles', id: 'str | None' = None, name: 'str | None' = None, program_name: 'str | None' = None, program_version: 'str | None' = None, background_color: 'str | None' = None) -> None` RenderInformation(*, list_of_color_definitions: 'ListOfColorDefinitions', list_of_gradient_definitions: 'ListOfGradientDefinitions', list_of_styles: 'ListOfStyles', id: 'str | None' = None, name: 'str | None' = None, program_name: 'str | None' = None, program_version: 'str | None' = None, background_color: 'str | None' = None) ### `RenderInformation.Meta()` ## class `Style(*, g: 'G', id_list: 'str | None' = None, role_list: 'str | None' = None, type_list: 'str | None' = None) -> None` Style(*, g: 'G', id_list: 'str | None' = None, role_list: 'str | None' = None, type_list: 'str | None' = None) ### `Style.Meta()` --- # libsbgnpy.io Reading and writing of SBGN documents. The functions in this module are the entry points of the package: an SBGN document is read into the [`Sbgn`][libsbgnpy.sbgn.Sbgn] object tree of `libsbgnpy.sbgn` and serialized back to SBGN-ML with [xsdata](https://github.com/tefra/xsdata). ```python from pathlib import Path from libsbgnpy import read_sbgn_from_file, write_sbgn_to_file sbgn = read_sbgn_from_file(Path("map.sbgn")) write_sbgn_to_file(sbgn, Path("map_copy.sbgn")) ``` ## function `element_from_string(xml_str: str) -> xsdata.formats.dataclass.models.generics.AnyElement` Parse raw XML into an entry of a `notes` or `extension` element. Args: xml_str: XML of a single element Returns: The element tree. Raises: lxml.etree.XMLSyntaxError: if the string is no well-formed XML ## function `element_to_string(element: object) -> str` Serialize the raw XML of a `notes` or `extension` entry. The content of `notes` and `extension` is arbitrary XML. It is set as a string, but read back as an [`AnyElement`](https://xsdata.readthedocs.io/en/latest/api/models/) tree, so this function turns such an entry back into XML. Args: element: entry of `Sbgnbase.Notes` or `Sbgnbase.Extension` Returns: The XML of the entry. Raises: TypeError: if the entry is neither a string nor an element tree ValueError: if the element tree carries no element name Examples: >>> from libsbgnpy import element_to_string, read_sbgn_from_file >>> sbgn = read_sbgn_from_file(f) # doctest: +SKIP >>> notes = sbgn.map[0].glyph[0].notes # doctest: +SKIP >>> element_to_string(notes.w3_org_1999_xhtml_element[0]) # doctest: +SKIP 'note' ## function `read_render_from_extension(extension: libsbgnpy.sbgn.Sbgnbase.Extension | None) -> libsbgnpy.render.RenderInformation | None` Read the render information stored in an extension. Render information is stored as raw XML in the `extension` of an SBGN element, see `libsbgnpy.render`; the first `renderInformation` entry of the extension is returned. Args: extension: extension of an SBGN element, e.g., of a map Returns: The render information, or `None` if the extension contains none. Raises: xsdata.exceptions.ParserError: if the entry is no render information ## function `read_render_from_string(xml_str: str) -> libsbgnpy.render.RenderInformation` Read render information from a string. Render information is stored in the `extension` of an SBGN element, see `libsbgnpy.render`. Args: xml_str: `renderInformation` document Returns: The render information. Raises: xsdata.exceptions.ParserError: if the content is no render information ## function `read_sbgn_from_file(f: pathlib.Path) -> libsbgnpy.sbgn.Sbgn` Read an SBGN document from a file. The document is not validated against the schema, see [`validate_xsd`][libsbgnpy.validator.validate_xsd]. SBGN-ML 0.1 and 0.2 documents are upconverted while reading. Args: f: path of the SBGN file Returns: The SBGN document. Raises: OSError: if the file cannot be read xsdata.exceptions.ParserError: if the content is no valid SBGN-ML ## function `read_sbgn_from_string(xml_str: str) -> libsbgnpy.sbgn.Sbgn` Read an SBGN document from a string. Args: xml_str: SBGN-ML document Returns: The SBGN document. Raises: xsdata.exceptions.ParserError: if the content is no valid SBGN-ML ## function `upconvert(xml_str: str) -> str` Replace an SBGN-ML 0.1 or 0.2 namespace with the 0.3 namespace. The bindings are generated from the SBGN-ML 0.3 schema, the earlier versions are read by upconverting the document. Args: xml_str: SBGN-ML document Returns: The document in the `SBGN_NAMESPACE`. ## function `write_render_to_string(render_info: libsbgnpy.render.RenderInformation) -> str` Serialize render information to a string. The result is written without an XML declaration and without a namespace prefix, so that it can be stored in the `extension` of an SBGN element. Args: render_info: render information Returns: The `renderInformation` document. ## function `write_sbgn_to_file(sbgn: libsbgnpy.sbgn.Sbgn, f: pathlib.Path) -> None` Write an SBGN document to a file. Args: sbgn: SBGN document f: path of the file to write Raises: OSError: if the file cannot be written ## function `write_sbgn_to_string(sbgn: libsbgnpy.sbgn.Sbgn) -> str` Serialize an SBGN document to an SBGN-ML string. The raw XML of the `notes` and `extension` elements is converted into element trees first, see `element_from_string`, so that it is written as markup instead of as escaped text. Args: sbgn: SBGN document Returns: The SBGN-ML document, indented with two spaces. --- # libsbgnpy.validator Validation of SBGN documents against the SBGN XSD schema. The packaged schema is the SBGN-ML 0.3 schema in `libsbgnpy/schema/SBGN.xsd`, documents in the earlier namespaces are upconverted before they are validated, i.e., the same documents are read and validated. ```python from pathlib import Path from libsbgnpy import validate_xsd errors = validate_xsd(Path("map.sbgn")) if errors: for error in errors: print(error) ``` ## function `validate_xsd(f: pathlib.Path) -> list[str]` Validate an SBGN file against the SBGN XSD schema. Args: f: path of the SBGN file Returns: The validation errors, empty if the document is valid. Raises: OSError: if the file cannot be read --- # libsbgnpy.image Rendering of SBGN documents as images. The rendering is performed by the web service of Frank Bergmann at , i.e., it requires an internet connection. For the documentation of the service see . ```python from pathlib import Path from libsbgnpy import read_sbgn_from_file, render_sbgn sbgn = read_sbgn_from_file(Path("map.sbgn")) render_sbgn(sbgn, Path("map.png")) ``` ## function `render_sbgn(sbgn: libsbgnpy.sbgn.Sbgn, image_file: pathlib.Path, file_format: str = 'png') -> None` Render an SBGN document to an image. The document is sent to the rendering web service, which lays the map out and returns the image. The request is equivalent to ```bash curl -X POST -F file=@"map.sbgn" https://sbml.bioquant.uni-heidelberg.de/layout -o map.png ``` Args: sbgn: SBGN document image_file: path of the image to create, ending in `.` file_format: image format, only `png` is supported Raises: ValueError: if the format is not supported or the file has another suffix requests.RequestException: if the web service cannot be reached or fails --- # libsbgnpy.console Shared rich console. The console is used for the output of scripts and examples; library code logs instead of printing, see `libsbgnpy.log`. ```python from libsbgnpy.console import console console.print(sbgn) console.rule("Glyphs", style="white") ``` Importing this module has no side effects on the interpreter. To get rich representations in an interactive session, install them explicitly with `rich.pretty.install()`. --- # libsbgnpy.log Logging of the package. `libsbgnpy` follows the convention for libraries: it only gets loggers and logs to them, it does not configure logging. Handlers, levels and formatting are left to the application, which keeps the messages of the package under the control of whoever uses it. Modules get their logger from the standard library with ```python import logging logger = logging.getLogger(__name__) ``` All loggers are therefore below the `libsbgnpy` logger, so an application configures them in one place: ```python import logging logging.getLogger("libsbgnpy").setLevel(logging.WARNING) ``` For scripts and interactive work the rich formatting of the package can be enabled explicitly, which is what the examples do: ```python from libsbgnpy import log log.enable_rich_logging() ``` ## function `enable_rich_logging(level: int = 20, console: rich.console.Console | None = None) -> logging.Logger` Log the messages of the package on a rich console. This configures logging and is meant for scripts, examples and interactive work. Applications should configure logging themselves instead of calling this. Calling it repeatedly replaces the handler instead of adding a second one. Args: level: level from which messages are logged console: console to log on, the console of the package by default Returns: The `libsbgnpy` logger. --- # Development Contributions are welcome. The repository is [matthiaskoenig/libsbgnpy](https://github.com/matthiaskoenig/libsbgnpy); development happens against the `develop` branch via pull requests. ## Branch model Two branches are permanent: - **`develop`** is the default branch and the branch everything is integrated into. The documentation on [matthiaskoenig.github.io/libsbgnpy](https://matthiaskoenig.github.io/libsbgnpy) is published from it. - **`main`** tracks the latest published release. It is fast-forwarded to the released commit by the `sync-main` job of the `CI-CD` workflow after the package went to pypi, so `main` and the newest version on pypi always agree. Nothing is developed on `main` and nothing is merged into it by hand. Work happens on short lived branches off `develop`, which GitHub deletes after the merge. Releases are tagged on `develop`, see [Release](#release). ## Pull requests Neither branch accepts a direct push, every change goes through a pull request against `develop`. This includes the maintainer, there is no bypass. A pull request can only be merged once the four required checks are green: | check | workflow | content | | ------- | ------------- | -------------------------------------------------------------------- | | `tests` | `ci-cd.yml` | the test matrix, linux, macos and windows with python 3.11 to 3.14 | | `ruff` | `ruff.yml` | `ruff check` and `ruff format --check` | | `ty` | `ty.yml` | `tox r -e ty` | | `docs` | `docs.yml` | the zensical build including the api reference and the agent files | `tests` aggregates the test matrix into a single job, so the name of the required check stays the same when the matrix changes. Further rules of a pull request: - conversations have to be resolved before the merge - an approval is dismissed when new commits are pushed - the history stays linear, i.e., a pull request is merged with squash or rebase; merge commits are disabled - the maintainer is the code owner of the repository (`.github/CODEOWNERS`) and is requested for review on every pull request. A pull request of a contributor is therefore reviewed and merged by the maintainer, who has the only write access. The rulesets themselves do not require an approval: on a personal repository a ruleset cannot ask for an approval only from somebody else, and requiring one would block the pull requests of the maintainer, who cannot approve their own. Once a second person has write access, a ruleset requiring an approving review of a code owner can be added [Auto-merge](https://docs.github.com/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/automatically-merging-a-pull-request) is enabled for the repository, so a pull request can be queued and is merged as soon as the checks pass and the required approval is there. ### Repository policies { #repository-policies } The protection is implemented with [repository rulesets](https://docs.github.com/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets). They are part of the repository in `.github/rulesets/` instead of only living in the web interface, so a change to a policy is reviewed like any other change: | ruleset | applies to | rules | | ----------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `develop.json` | `develop` | pull request required, the four checks above, resolved conversations, linear history, no force push, no deletion. **No bypass, for anybody.** | | `main.json` | `main` | linear history, no force push, no deletion, no bypass. The fast-forward of the release workflow needs none, only a force push or a merge commit would be rejected | | `tags.json` | all tags | a tag cannot be deleted or moved, so a release tag keeps pointing at what was released | Changing a policy means changing the json and applying it: ```bash .github/rulesets/apply.sh ``` The script is idempotent: it updates the rulesets which exist and creates the missing ones. It also sets the merge settings of the repository, i.e., auto-merge, delete branch on merge, and squash and rebase as the only merge methods. It needs the [github cli](https://cli.github.com) authenticated as a user with admin permission on the repository. ## Setup development environment Development needs [uv](https://docs.astral.sh/uv/) and a checkout of the repository: ```bash git clone https://github.com/matthiaskoenig/libsbgnpy.git cd libsbgnpy ``` A single sync creates the virtual environment in `.venv`, installs `libsbgnpy` into it in editable mode and adds the complete tooling: ```bash uv sync --extra dev ``` The `dev` extra contains everything used below, i.e., pytest, ruff, ty, tox, pre-commit, zensical and bump-my-version, so nothing has to be installed separately. The python version is taken from `.python-version` (currently 3.14); to work against the oldest supported version instead use `uv sync --extra dev --python 3.11`, which replaces the environment. The tools are then run either with `uv run `, which uses the environment without activating it, or from the activated environment: ```bash source .venv/bin/activate # Linux and macOS .venv\Scripts\activate # Windows ``` The commands in this document are written without the `uv run` prefix; prepend it if the environment is not activated. The last step installs the git hook: ```bash uv run pre-commit install # install the hook, once per checkout uv run pre-commit run --all-files # check the current state of the repository ``` From now on every commit is checked with ruff (lint and format) and ty, i.e., the same checks that run in continuous integration. On a commit only the changed files are looked at, `--all-files` checks the whole repository and is what a newly added hook should be tried with. ## Testing The tests are written with pytest, tox runs them against every supported python version. The tox environments are named after the interpreter (`py3.11` to `py3.14`, see `envlist` in `tox.ini`), a single one is run with ```bash tox r -e py3.14 ``` and the complete matrix, including the `ty` environment, in parallel with ```bash tox run-parallel ``` This needs the interpreters to be available, which uv installs with `uv python install 3.11 3.12 3.13 3.14`. Continuous integration runs the same environments as `uvx --with tox-uv tox -e py3.14`. To run the tests directly against the development environment use ```bash pytest # the full suite pytest tests/test_io.py # a single module pytest tests/test_io.py::test_upconvert # a single test ``` `tests/data` holds the reference maps of the SBGN specifications, one directory per map language. `tests/test_data.py` reads, writes and validates every one of them, so a change to the bindings or to the io is checked against the whole corpus. `tests/test_examples.py` runs every example of `examples/` in a temporary working directory, which keeps the examples of the documentation working. The tests of `tests/test_image.py` query the rendering web service and therefore require network access. ## Linting and formatting Linting and formatting use [ruff](https://docs.astral.sh/ruff/): ```bash ruff check # lint ruff format # format ``` The generated bindings, `libsbgnpy/sbgn.py` and `libsbgnpy/render.py`, are excluded from the docstring rules, see `[lint.per-file-ignores]` in `.ruff.toml`; their docstrings are the documentation of the schema. ## Type checking Type checking is performed with [ty](https://docs.astral.sh/ty/): ```bash tox r -e ty ``` Or directly in the working tree: ```bash uvx ty check ``` The configuration lives in `[tool.ty]` in `pyproject.toml`. Warnings are treated as errors, so the codebase is kept free of diagnostics. Suppress an unavoidable diagnostic with a rule specific `# ty: ignore[rule-name]` rather than a blanket comment. ## Documentation The documentation is built with [Zensical](https://zensical.org/), the static site generator of the Material for MkDocs authors. The sources are markdown files in `docs/`, the site is configured in `zensical.toml` in the repository root. Nothing rendered is committed: the site is built by the `documentation` workflow on every push and published to [matthiaskoenig.github.io/libsbgnpy](https://matthiaskoenig.github.io/libsbgnpy) from the `develop` branch. Build the site into `site/`: ```bash uv run zensical build --clean ``` For writing, the preview rebuilds on save: ```bash uv run zensical serve ``` The API reference is rendered from the docstrings by [mkdocstrings](https://mkdocstrings.github.io/); a page in `docs/api/` only contains the module directive: ```markdown # io ::: libsbgnpy.io ``` Docstrings are therefore the place to document functions and classes, the markdown files provide the narrative around them. Adding a module to the reference means adding such a page and an entry to `nav` in `zensical.toml`. ### Files for agents { #files-for-agents } Agents and language models read markdown, not rendered html. `scripts/llms_txt.py` writes the files of the [llms.txt convention](https://llmstxt.org/) into the built site, i.e., [llms.txt](https://matthiaskoenig.github.io/libsbgnpy/llms.txt) as an annotated index of all pages, [llms-full.txt](https://matthiaskoenig.github.io/libsbgnpy/llms-full.txt) with the complete documentation in a single file, and the markdown of every page next to its html (`/io.md` for `/io/`). The markdown of the API reference is generated from the docstrings with `inspect`, since the pages themselves only contain the mkdocstrings directive. ```bash uv run zensical build --clean uv run python scripts/llms_txt.py ``` The `documentation` workflow runs both steps, so the files are regenerated with every push. `docs/robots.txt` points crawlers at the sitemap and at these files. ## Regenerating the bindings { #regenerating-the-bindings } `libsbgnpy.sbgn` and `libsbgnpy.render` are generated modules and should not be edited by hand. They are generated from the schemas in `src/libsbgnpy/schema/` with [xsdata](https://github.com/tefra/xsdata); the current schemas are published with [sbgn/libsbgn](https://github.com/sbgn/libsbgn) in the `resources` folder. The procedure and the manual fixes which are applied afterwards are described in `src/libsbgnpy/schema/README.md`. Run `ruff format` and `ruff check --fix` afterwards, the generated modules are neither formatted nor on current python syntax. ## Work in progress `libsbgnpy.oven` collects unfinished work: it is not wired into the package, not documented, not part of the public API and not shipped in the release, see `[tool.hatch.build]` in `pyproject.toml`. It currently holds the mapping of SBO terms to SBGN glyphs, groundwork for the conversion of SBML to SBGN, see [issue #52](https://github.com/matthiaskoenig/libsbgnpy/issues/52). ## Release A release is made from `develop`. Since `develop` only accepts pull requests, the release is prepared on a branch and tagged once that pull request is merged: 1. branch off `develop`: `git switch -c release/x.y.z develop` 2. write the release notes for the version in `release-notes/x.y.z.md` 3. make sure everything passes: `tox run-parallel`, `ruff check`, `tox r -e ty` 4. check the version bump: `uvx bump-my-version bump [major|minor|patch] --dry-run -vv` 5. bump the version: `uvx bump-my-version bump [major|minor|patch]`, which updates `src/libsbgnpy/__init__.py` and `CITATION.cff` and commits. It does not create the tag; a squash or rebase merge would rewrite the commit and leave the tag behind on a commit which is not part of `develop` 6. push the branch, open the pull request against `develop` and merge it once the checks are green 7. tag the merged commit on `develop` and push the tag: ```bash git switch develop git pull git tag x.y.z git push origin x.y.z ``` This starts the `CI-CD` workflow, which runs the test matrix, publishes to [pypi](https://pypi.org/project/libsbgnpy/), creates the GitHub release from `release-notes/x.y.z.md` and fast-forwards `main` to the tagged commit. Check the version before pushing, a tag cannot be moved or deleted afterwards. 8. test the installation from pypi in a fresh environment: ```bash uv venv --python 3.14 uv pip install libsbgnpy ``` 9. once Zenodo has archived the release, update the citation information: `date-released` and the version DOI in `doi` and `identifiers` of `CITATION.cff`, and the citation and the bibtex entry of the `How to cite` section of `docs/index.md`. `bump-my-version` only updates the version, neither the date nor the DOI, which are only known after the release. The badges and the `README.md` carry the concept DOI, which always resolves to the latest version, and stay as they are. These changes go in through a pull request like everything else