Skip to content

Commit 7cc6e89

Browse files
committed
v2: Store path info in *Table objects
Store path info in *Table and Model objects to make it easier to read, modify, write complete PEtab problem. Add `Problem.to_files()`. Closes #412.
1 parent aeced6d commit 7cc6e89

File tree

6 files changed

+283
-71
lines changed

6 files changed

+283
-71
lines changed

petab/_utils.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""Private, version-independent utility functions for PEtab."""
2+
3+
from pathlib import Path
4+
5+
from pydantic import AnyUrl, TypeAdapter
6+
7+
PathOrUrlAdapter = TypeAdapter(AnyUrl | Path)
8+
9+
10+
def _generate_path(
11+
file_path: str | Path | AnyUrl,
12+
base_path: Path | str | AnyUrl | None = None,
13+
) -> str:
14+
"""
15+
Generate a local path or URL from a file path and an optional base path.
16+
17+
:return: A string representing the relative or absolute path or URL.
18+
Absolute if `file_path` or `base_path` is an absolute path or URL,
19+
relative otherwise.
20+
"""
21+
if base_path is None:
22+
return str(file_path)
23+
24+
file_path = PathOrUrlAdapter.validate_python(file_path)
25+
if isinstance(file_path, AnyUrl):
26+
# if URL, this is absolute
27+
return str(file_path)
28+
29+
base_path = PathOrUrlAdapter.validate_python(base_path)
30+
if isinstance(base_path, Path):
31+
# if file_path is absolute, base_path will be ignored
32+
return str(base_path / file_path)
33+
34+
# combine URL parts
35+
return f"{base_path}/{file_path}"

petab/v1/models/model.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,22 @@ def __repr__(self):
2121

2222
@staticmethod
2323
@abc.abstractmethod
24-
def from_file(filepath_or_buffer: Any, model_id: str) -> Model:
24+
def from_file(
25+
filepath_or_buffer: Any, model_id: str, base_path: str | Path = None
26+
) -> Model:
2527
"""Load the model from the given path/URL
2628
27-
:param filepath_or_buffer: URL or path of the model
29+
:param filepath_or_buffer:
30+
Absolute or relative path/URL to the model file.
31+
If relative, it is interpreted relative to `base_path` if given.
32+
:param base_path: Base path for relative paths in the model file.
2833
:param model_id: Model ID
2934
:returns: A ``Model`` instance holding the given model
3035
"""
3136
...
3237

3338
@abc.abstractmethod
34-
def to_file(self, filename: [str, Path]):
39+
def to_file(self, filename: str | Path | None = None):
3540
"""Save the model to the given file
3641
3742
:param filename: Destination filename
@@ -131,11 +136,16 @@ def is_state_variable(self, id_: str) -> bool:
131136

132137

133138
def model_factory(
134-
filepath_or_buffer: Any, model_language: str, model_id: str = None
139+
filepath_or_buffer: Any,
140+
model_language: str,
141+
model_id: str = None,
142+
base_path: str | Path = None,
135143
) -> Model:
136144
"""Create a PEtab model instance from the given model
137145
138-
:param filepath_or_buffer: Path/URL of the model
146+
:param filepath_or_buffer: Path/URL of the model.
147+
Absolute or relative to `base_path` if given.
148+
:param base_path: Base path for relative paths in the model file.
139149
:param model_language: PEtab model language ID for the given model
140150
:param model_id: PEtab model ID for the given model
141151
:returns: A :py:class:`Model` instance representing the given model
@@ -145,12 +155,16 @@ def model_factory(
145155
if model_language == MODEL_TYPE_SBML:
146156
from .sbml_model import SbmlModel
147157

148-
return SbmlModel.from_file(filepath_or_buffer, model_id=model_id)
158+
return SbmlModel.from_file(
159+
filepath_or_buffer, model_id=model_id, base_path=base_path
160+
)
149161

150162
if model_language == MODEL_TYPE_PYSB:
151163
from .pysb_model import PySBModel
152164

153-
return PySBModel.from_file(filepath_or_buffer, model_id=model_id)
165+
return PySBModel.from_file(
166+
filepath_or_buffer, model_id=model_id, base_path=base_path
167+
)
154168

155169
if model_language in known_model_types:
156170
raise NotImplementedError(

petab/v1/models/pysb_model.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Functions for handling PySB models"""
22

3+
from __future__ import annotations
4+
35
import itertools
46
import re
57
import sys
@@ -9,6 +11,7 @@
911

1012
import pysb
1113

14+
from ..._utils import _generate_path
1215
from .. import is_valid_identifier
1316
from . import MODEL_TYPE_PYSB
1417
from .model import Model
@@ -54,9 +57,18 @@ class PySBModel(Model):
5457

5558
type_id = MODEL_TYPE_PYSB
5659

57-
def __init__(self, model: pysb.Model, model_id: str = None):
60+
def __init__(
61+
self,
62+
model: pysb.Model,
63+
model_id: str = None,
64+
rel_path: Path | str | None = None,
65+
base_path: str | Path | None = None,
66+
):
5867
super().__init__()
5968

69+
self.rel_path = rel_path
70+
self.base_path = base_path
71+
6072
self.model = model
6173
self._model_id = model_id or self.model.name
6274

@@ -68,16 +80,25 @@ def __init__(self, model: pysb.Model, model_id: str = None):
6880
)
6981

7082
@staticmethod
71-
def from_file(filepath_or_buffer, model_id: str = None):
83+
def from_file(
84+
filepath_or_buffer, model_id: str = None, base_path: str | Path = None
85+
) -> PySBModel:
7286
return PySBModel(
73-
model=_pysb_model_from_path(filepath_or_buffer), model_id=model_id
87+
model=_pysb_model_from_path(
88+
_generate_path(filepath_or_buffer, base_path)
89+
),
90+
model_id=model_id,
91+
rel_path=filepath_or_buffer,
92+
base_path=base_path,
7493
)
7594

76-
def to_file(self, filename: [str, Path]):
95+
def to_file(self, filename: str | Path | None = None) -> None:
7796
from pysb.export import export
7897

7998
model_source = export(self.model, "pysb_flat")
80-
with open(filename, "w") as f:
99+
with open(
100+
filename or _generate_path(self.rel_path, self.base_path), "w"
101+
) as f:
81102
f.write(model_source)
82103

83104
@property

petab/v1/models/sbml_model.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import sympy as sp
1111
from sympy.abc import _clash
1212

13+
from ..._utils import _generate_path
1314
from ..sbml import (
1415
get_sbml_model,
1516
is_sbml_consistent,
@@ -33,6 +34,8 @@ def __init__(
3334
sbml_reader: libsbml.SBMLReader = None,
3435
sbml_document: libsbml.SBMLDocument = None,
3536
model_id: str = None,
37+
rel_path: Path | str | None = None,
38+
base_path: str | Path | None = None,
3639
):
3740
"""Constructor.
3841
@@ -42,6 +45,9 @@ def __init__(
4245
:param model_id: Model ID. Defaults to the SBML model ID."""
4346
super().__init__()
4447

48+
self.rel_path = rel_path
49+
self.base_path = base_path
50+
4551
if sbml_model is None and sbml_document is None:
4652
raise ValueError(
4753
"Either sbml_model or sbml_document must be given."
@@ -87,15 +93,19 @@ def __setstate__(self, state):
8793
self.__dict__.update(state)
8894

8995
@staticmethod
90-
def from_file(filepath_or_buffer, model_id: str = None) -> SbmlModel:
96+
def from_file(
97+
filepath_or_buffer, model_id: str = None, base_path: str | Path = None
98+
) -> SbmlModel:
9199
sbml_reader, sbml_document, sbml_model = get_sbml_model(
92-
filepath_or_buffer
100+
_generate_path(filepath_or_buffer, base_path=base_path)
93101
)
94102
return SbmlModel(
95103
sbml_model=sbml_model,
96104
sbml_reader=sbml_reader,
97105
sbml_document=sbml_document,
98106
model_id=model_id,
107+
rel_path=filepath_or_buffer,
108+
base_path=base_path,
99109
)
100110

101111
@staticmethod
@@ -159,9 +169,10 @@ def model_id(self):
159169
def model_id(self, model_id):
160170
self._model_id = model_id
161171

162-
def to_file(self, filename: [str, Path]):
172+
def to_file(self, filename: str | Path | None = None) -> None:
163173
write_sbml(
164-
self.sbml_document or self.sbml_model.getSBMLDocument(), filename
174+
self.sbml_document or self.sbml_model.getSBMLDocument(),
175+
filename or _generate_path(self.rel_path, self.base_path),
165176
)
166177

167178
def get_parameter_value(self, id_: str) -> float:

0 commit comments

Comments
 (0)