Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/sphinx/source/api/Contributors.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Modelspec contributors

This page list names and Github profiles of contributors to Modelspec, listed in no particular order.
This page is generated periodically, most recently on 2026-06-10.
This page is generated periodically, most recently on 2026-06-24.

- Padraig Gleeson ([@pgleeson](https://github.com/pgleeson))
- Manifest Chakalov ([@mqnifestkelvin](https://github.com/mqnifestkelvin))
Expand Down
89 changes: 0 additions & 89 deletions examples/sbml/SBML.md
Original file line number Diff line number Diff line change
Expand Up @@ -1670,95 +1670,6 @@ XHTML field of SBase
</tr>


</table>

## SpeciesReference
### Allowed parameters
<table>
<tr>
<td><b>sid</b></td>
<td>str</td>
<td><i> SId optional</i></td>
</tr>


<tr>
<td><b>name</b></td>
<td>str</td>
<td><i> string optional</i></td>
</tr>


<tr>
<td><b>metaid</b></td>
<td>str</td>
<td><i> XML ID optional</i></td>
</tr>


<tr>
<td><b>sboTerm</b></td>
<td>str</td>
<td><i>SBOTerm optional</i></td>
</tr>


<tr>
<td><b>notes</b></td>
<td><a href="#notes">Notes</a></td>
<td><i> XHTML 1.0 optional</i></td>
</tr>


<tr>
<td><b>annotation</b></td>
<td>str</td>
<td><i>XML content optional</i></td>
</tr>


<tr>
<td><b>species</b></td>
<td>str</td>
<td><i>SIdRef</i></td>
</tr>


<tr>
<td><b>stoichiometry</b></td>
<td>float</td>
<td><i>double optional</i></td>
</tr>


<tr>
<td><b>constant</b></td>
<td>bool</td>
<td><i>boolean</i></td>
</tr>


</table>

## Notes
XHTML field of SBase

### Allowed parameters
<table>
<tr>
<td><b>xmlns</b></td>
<td>str</td>
<td><i>str fixed "http://www.w3.org/1999/xhtml"</i></td>
</tr>


<tr>
<td><b>content</b></td>
<td>str</td>
<td><i>str valid XHTML</i></td>
</tr>


</table>

## ModifierSpeciesReference
Expand Down
33 changes: 0 additions & 33 deletions examples/sbml/SBML.rst
Original file line number Diff line number Diff line change
Expand Up @@ -650,39 +650,6 @@ Allowed field Data Type Description
**content** str str valid XHTML
=============== =========== ========================================

================
SpeciesReference
================
**Allowed parameters**

================= ======================================= ====================
Allowed field Data Type Description
================= ======================================= ====================
**sid** str SId optional
**name** str string optional
**metaid** str XML ID optional
**sboTerm** str SBOTerm optional
**notes** `<class 'sbml32spec.Notes'> <#notes>`__ XHTML 1.0 optional
**annotation** str XML content optional
**species** str SIdRef
**stoichiometry** float double optional
**constant** bool boolean
================= ======================================= ====================

=====
Notes
=====
XHTML field of SBase

**Allowed parameters**

=============== =========== ========================================
Allowed field Data Type Description
=============== =========== ========================================
**xmlns** str str fixed "http://www.w3.org/1999/xhtml"
**content** str str valid XHTML
=============== =========== ========================================

========================
ModifierSpeciesReference
========================
Expand Down
2 changes: 1 addition & 1 deletion src/modelspec/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "0.3.10"
__version__ = "0.4.0"

from .base_types import Base, define, has, field, fields, optional, instance_of, in_

Expand Down
41 changes: 31 additions & 10 deletions src/modelspec/base_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ class EvaluableExpression(str):
str, so it can be used as a string.
"""

def __init__(self, expr):
self.expr = expr
@property
def expr(self):
"""The expression string. Always reflects the underlying str value."""
return str(self)


# Union of types that are allowed as value expressions for parameters.
Expand All @@ -62,7 +64,7 @@ def print_(text: str, print_it: bool = False):

prefix = "modelspec >>> "
if not isinstance(text, str):
text = ("%s" % text).decode("ascii")
text = "%s" % text
print("{}{}".format(prefix, text.replace("\n", "\n" + prefix)))


Expand Down Expand Up @@ -111,9 +113,9 @@ def to_json(self) -> str:
"""
return json.dumps(self.to_dict(), indent=4)

def to_bson(self) -> str:
def to_bson(self) -> bytes:
"""
Convert the Base object to a BSON string representation.
Convert the Base object to a BSON (bytes) representation.
"""
return bson.encode(self.to_dict())

Expand Down Expand Up @@ -238,7 +240,9 @@ def to_json_file(

return filename

def to_bson_file(self, filename: str, include_metadata: bool = True) -> str:
def to_bson_file(
self, filename: Optional[str] = None, include_metadata: bool = True
) -> str:
"""Convert modelspec format to bson format
Args:
Expand Down Expand Up @@ -321,7 +325,8 @@ def to_xml_file(
def from_file(cls, filename: str) -> "Base":
"""
Create a :class:`.Base` from its representation stored in a file. Auto-detect the correct deserialization code
based on file extension. Currently supported formats are; JSON(.json) and YAML (.yaml or .yml)
based on file extension. Currently supported formats are: JSON (.json), YAML (.yaml or .yml),
BSON (.bson) and XML (.xml).
Args:
filename: The name of the file to load.
Expand All @@ -340,7 +345,7 @@ def from_file(cls, filename: str) -> "Base":
else:
raise ValueError(
f"Cannot auto-detect modelspec serialization format from filename ({filename}). The filename "
f"must have one of the following extensions: .json, .yml, or .yaml."
f"must have one of the following extensions: .json, .yaml, .yml, .bson, or .xml."
)

@classmethod
Expand Down Expand Up @@ -945,10 +950,19 @@ def insert_links(text, format=MARKDOWN_FORMAT):
)
)

# De-duplicate while preserving order, so a type referenced by more than
# one field/child doesn't get its documentation section emitted twice.
seen = set()
unique_referenced = []
for r in referenced:
if r not in seen:
seen.add(r)
unique_referenced.append(r)

for r in unique_referenced:
if format in (MARKDOWN_FORMAT, RST_FORMAT):
doc_string += r._cls_generate_documentation(format=format)
if format in (DICT_FORMAT):
if format == DICT_FORMAT:
doc_dict.update(r._cls_generate_documentation(format=format))

if format in (MARKDOWN_FORMAT, RST_FORMAT):
Expand Down Expand Up @@ -1090,7 +1104,14 @@ def _is_list_base(cl):
Check if a class is a list of Base objects. These will be serialized as dicts if the underlying class has an id
attribute.
"""
return get_origin(cl) is list and issubclass(get_args(cl)[0], Base)
if get_origin(cl) is not list:
return False

args = get_args(cl)
# Guard against a bare ``list`` annotation (no args) and against element
# types that aren't classes (e.g. List[Union[A, B]]), which would make
# issubclass() raise TypeError.
return len(args) > 0 and isinstance(args[0], type) and issubclass(args[0], Base)


converter.register_unstructure_hook_factory(_is_list_base, _unstructure_list_base)
Expand Down
16 changes: 11 additions & 5 deletions src/modelspec/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ def _parse_attributes(dict_format, to_build):
ff = type_to_use()
print_(f" Type for {key}: {type_to_use} ({ff})", verbose)
ff = _parse_element({v: value[v]}, ff)
exec("to_build.%s.append(ff)" % key)
getattr(to_build, key).append(ff)
else:
if (
isinstance(value, str)
Expand All @@ -361,7 +361,7 @@ def _parse_attributes(dict_format, to_build):
else:
ff = type_to_use()
ff = _parse_attributes(value, ff)
exec("to_build.%s = ff" % key)
setattr(to_build, key, ff)

else:
if isinstance(to_build, dict):
Expand All @@ -378,12 +378,12 @@ def _parse_attributes(dict_format, to_build):
for vl in value:
ff = type_to_use()
ff = _parse_element(vl, ff)
exec("to_build.%s.append(ff)" % key)
getattr(to_build, key).append(ff)
else:
type_to_use = to_build.allowed_fields[key][1]
ff = type_to_use()
ff = _parse_attributes(value, ff)
exec("to_build.%s = ff" % key)
setattr(to_build, key, ff)

return to_build

Expand Down Expand Up @@ -445,7 +445,7 @@ def _params_info(parameters, multiline=False):

def evaluate(
expr: Union[int, float, str, list, dict],
parameters: dict = {},
parameters: dict = None,
rng: Random = None,
array_format: str = FORMAT_NUMPY,
verbose: bool = False,
Expand All @@ -465,6 +465,10 @@ def evaluate(
cast_to_int: return an int for float/string values if castable
"""

# Work on a private copy so we never mutate the caller's dict (or a shared
# default) when injecting rng/math/numpy below or when eval() adds __builtins__.
parameters = dict(parameters) if parameters is not None else {}

if array_format == FORMAT_TENSORFLOW:
import tensorflow as tf

Expand Down Expand Up @@ -591,3 +595,5 @@ def parse_list_like(list_str):
pass
if "[" in list_str:
return eval(list_str)

raise ValueError(f"Cannot parse {list_str!r} ({type(list_str)}) as a list")
Loading