Skip to content

Commit 6769194

Browse files
authored
Merge pull request #101 from ModECI/experimental
More internal fixes/tidying
2 parents 5a1312c + 197354b commit 6769194

6 files changed

Lines changed: 44 additions & 139 deletions

File tree

docs/sphinx/source/api/Contributors.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Modelspec contributors
44

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

88
- Padraig Gleeson ([@pgleeson](https://github.com/pgleeson))
99
- Manifest Chakalov ([@mqnifestkelvin](https://github.com/mqnifestkelvin))

examples/sbml/SBML.md

Lines changed: 0 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1670,95 +1670,6 @@ XHTML field of SBase
16701670
</tr>
16711671

16721672

1673-
</table>
1674-
1675-
## SpeciesReference
1676-
### Allowed parameters
1677-
<table>
1678-
<tr>
1679-
<td><b>sid</b></td>
1680-
<td>str</td>
1681-
<td><i> SId optional</i></td>
1682-
</tr>
1683-
1684-
1685-
<tr>
1686-
<td><b>name</b></td>
1687-
<td>str</td>
1688-
<td><i> string optional</i></td>
1689-
</tr>
1690-
1691-
1692-
<tr>
1693-
<td><b>metaid</b></td>
1694-
<td>str</td>
1695-
<td><i> XML ID optional</i></td>
1696-
</tr>
1697-
1698-
1699-
<tr>
1700-
<td><b>sboTerm</b></td>
1701-
<td>str</td>
1702-
<td><i>SBOTerm optional</i></td>
1703-
</tr>
1704-
1705-
1706-
<tr>
1707-
<td><b>notes</b></td>
1708-
<td><a href="#notes">Notes</a></td>
1709-
<td><i> XHTML 1.0 optional</i></td>
1710-
</tr>
1711-
1712-
1713-
<tr>
1714-
<td><b>annotation</b></td>
1715-
<td>str</td>
1716-
<td><i>XML content optional</i></td>
1717-
</tr>
1718-
1719-
1720-
<tr>
1721-
<td><b>species</b></td>
1722-
<td>str</td>
1723-
<td><i>SIdRef</i></td>
1724-
</tr>
1725-
1726-
1727-
<tr>
1728-
<td><b>stoichiometry</b></td>
1729-
<td>float</td>
1730-
<td><i>double optional</i></td>
1731-
</tr>
1732-
1733-
1734-
<tr>
1735-
<td><b>constant</b></td>
1736-
<td>bool</td>
1737-
<td><i>boolean</i></td>
1738-
</tr>
1739-
1740-
1741-
</table>
1742-
1743-
## Notes
1744-
XHTML field of SBase
1745-
1746-
### Allowed parameters
1747-
<table>
1748-
<tr>
1749-
<td><b>xmlns</b></td>
1750-
<td>str</td>
1751-
<td><i>str fixed "http://www.w3.org/1999/xhtml"</i></td>
1752-
</tr>
1753-
1754-
1755-
<tr>
1756-
<td><b>content</b></td>
1757-
<td>str</td>
1758-
<td><i>str valid XHTML</i></td>
1759-
</tr>
1760-
1761-
17621673
</table>
17631674

17641675
## ModifierSpeciesReference

examples/sbml/SBML.rst

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -650,39 +650,6 @@ Allowed field Data Type Description
650650
**content** str str valid XHTML
651651
=============== =========== ========================================
652652

653-
================
654-
SpeciesReference
655-
================
656-
**Allowed parameters**
657-
658-
================= ======================================= ====================
659-
Allowed field Data Type Description
660-
================= ======================================= ====================
661-
**sid** str SId optional
662-
**name** str string optional
663-
**metaid** str XML ID optional
664-
**sboTerm** str SBOTerm optional
665-
**notes** `<class 'sbml32spec.Notes'> <#notes>`__ XHTML 1.0 optional
666-
**annotation** str XML content optional
667-
**species** str SIdRef
668-
**stoichiometry** float double optional
669-
**constant** bool boolean
670-
================= ======================================= ====================
671-
672-
=====
673-
Notes
674-
=====
675-
XHTML field of SBase
676-
677-
**Allowed parameters**
678-
679-
=============== =========== ========================================
680-
Allowed field Data Type Description
681-
=============== =========== ========================================
682-
**xmlns** str str fixed "http://www.w3.org/1999/xhtml"
683-
**content** str str valid XHTML
684-
=============== =========== ========================================
685-
686653
========================
687654
ModifierSpeciesReference
688655
========================

src/modelspec/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "0.3.10"
1+
__version__ = "0.4.0"
22

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

src/modelspec/base_types.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,10 @@ class EvaluableExpression(str):
4444
str, so it can be used as a string.
4545
"""
4646

47-
def __init__(self, expr):
48-
self.expr = expr
47+
@property
48+
def expr(self):
49+
"""The expression string. Always reflects the underlying str value."""
50+
return str(self)
4951

5052

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

6365
prefix = "modelspec >>> "
6466
if not isinstance(text, str):
65-
text = ("%s" % text).decode("ascii")
67+
text = "%s" % text
6668
print("{}{}".format(prefix, text.replace("\n", "\n" + prefix)))
6769

6870

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

114-
def to_bson(self) -> str:
116+
def to_bson(self) -> bytes:
115117
"""
116-
Convert the Base object to a BSON string representation.
118+
Convert the Base object to a BSON (bytes) representation.
117119
"""
118120
return bson.encode(self.to_dict())
119121

@@ -238,7 +240,9 @@ def to_json_file(
238240

239241
return filename
240242

241-
def to_bson_file(self, filename: str, include_metadata: bool = True) -> str:
243+
def to_bson_file(
244+
self, filename: Optional[str] = None, include_metadata: bool = True
245+
) -> str:
242246
"""Convert modelspec format to bson format
243247
244248
Args:
@@ -321,7 +325,8 @@ def to_xml_file(
321325
def from_file(cls, filename: str) -> "Base":
322326
"""
323327
Create a :class:`.Base` from its representation stored in a file. Auto-detect the correct deserialization code
324-
based on file extension. Currently supported formats are; JSON(.json) and YAML (.yaml or .yml)
328+
based on file extension. Currently supported formats are: JSON (.json), YAML (.yaml or .yml),
329+
BSON (.bson) and XML (.xml).
325330
326331
Args:
327332
filename: The name of the file to load.
@@ -340,7 +345,7 @@ def from_file(cls, filename: str) -> "Base":
340345
else:
341346
raise ValueError(
342347
f"Cannot auto-detect modelspec serialization format from filename ({filename}). The filename "
343-
f"must have one of the following extensions: .json, .yml, or .yaml."
348+
f"must have one of the following extensions: .json, .yaml, .yml, .bson, or .xml."
344349
)
345350

346351
@classmethod
@@ -945,10 +950,19 @@ def insert_links(text, format=MARKDOWN_FORMAT):
945950
)
946951
)
947952

953+
# De-duplicate while preserving order, so a type referenced by more than
954+
# one field/child doesn't get its documentation section emitted twice.
955+
seen = set()
956+
unique_referenced = []
948957
for r in referenced:
958+
if r not in seen:
959+
seen.add(r)
960+
unique_referenced.append(r)
961+
962+
for r in unique_referenced:
949963
if format in (MARKDOWN_FORMAT, RST_FORMAT):
950964
doc_string += r._cls_generate_documentation(format=format)
951-
if format in (DICT_FORMAT):
965+
if format == DICT_FORMAT:
952966
doc_dict.update(r._cls_generate_documentation(format=format))
953967

954968
if format in (MARKDOWN_FORMAT, RST_FORMAT):
@@ -1090,7 +1104,14 @@ def _is_list_base(cl):
10901104
Check if a class is a list of Base objects. These will be serialized as dicts if the underlying class has an id
10911105
attribute.
10921106
"""
1093-
return get_origin(cl) is list and issubclass(get_args(cl)[0], Base)
1107+
if get_origin(cl) is not list:
1108+
return False
1109+
1110+
args = get_args(cl)
1111+
# Guard against a bare ``list`` annotation (no args) and against element
1112+
# types that aren't classes (e.g. List[Union[A, B]]), which would make
1113+
# issubclass() raise TypeError.
1114+
return len(args) > 0 and isinstance(args[0], type) and issubclass(args[0], Base)
10941115

10951116

10961117
converter.register_unstructure_hook_factory(_is_list_base, _unstructure_list_base)

src/modelspec/utils.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ def _parse_attributes(dict_format, to_build):
336336
ff = type_to_use()
337337
print_(f" Type for {key}: {type_to_use} ({ff})", verbose)
338338
ff = _parse_element({v: value[v]}, ff)
339-
exec("to_build.%s.append(ff)" % key)
339+
getattr(to_build, key).append(ff)
340340
else:
341341
if (
342342
isinstance(value, str)
@@ -361,7 +361,7 @@ def _parse_attributes(dict_format, to_build):
361361
else:
362362
ff = type_to_use()
363363
ff = _parse_attributes(value, ff)
364-
exec("to_build.%s = ff" % key)
364+
setattr(to_build, key, ff)
365365

366366
else:
367367
if isinstance(to_build, dict):
@@ -378,12 +378,12 @@ def _parse_attributes(dict_format, to_build):
378378
for vl in value:
379379
ff = type_to_use()
380380
ff = _parse_element(vl, ff)
381-
exec("to_build.%s.append(ff)" % key)
381+
getattr(to_build, key).append(ff)
382382
else:
383383
type_to_use = to_build.allowed_fields[key][1]
384384
ff = type_to_use()
385385
ff = _parse_attributes(value, ff)
386-
exec("to_build.%s = ff" % key)
386+
setattr(to_build, key, ff)
387387

388388
return to_build
389389

@@ -445,7 +445,7 @@ def _params_info(parameters, multiline=False):
445445

446446
def evaluate(
447447
expr: Union[int, float, str, list, dict],
448-
parameters: dict = {},
448+
parameters: dict = None,
449449
rng: Random = None,
450450
array_format: str = FORMAT_NUMPY,
451451
verbose: bool = False,
@@ -465,6 +465,10 @@ def evaluate(
465465
cast_to_int: return an int for float/string values if castable
466466
"""
467467

468+
# Work on a private copy so we never mutate the caller's dict (or a shared
469+
# default) when injecting rng/math/numpy below or when eval() adds __builtins__.
470+
parameters = dict(parameters) if parameters is not None else {}
471+
468472
if array_format == FORMAT_TENSORFLOW:
469473
import tensorflow as tf
470474

@@ -591,3 +595,5 @@ def parse_list_like(list_str):
591595
pass
592596
if "[" in list_str:
593597
return eval(list_str)
598+
599+
raise ValueError(f"Cannot parse {list_str!r} ({type(list_str)}) as a list")

0 commit comments

Comments
 (0)