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"))
```

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 operatorimplicit 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 
 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