Skip to content

Commit 9a4efb4

Browse files
authored
Enable passing the base path to Problem.from_yaml (PEtab-dev#327)
When passing the problem configuration as `dict` to `Problem.from_yaml`, one should be able to specify the base path for resolving relative paths. See PEtab-dev#324. Closes PEtab-dev#324
1 parent 0b77d7f commit 9a4efb4

File tree

4 files changed

+113
-14
lines changed

4 files changed

+113
-14
lines changed

petab/v1/problem.py

+11-4
Original file line numberDiff line numberDiff line change
@@ -251,21 +251,28 @@ def from_files(
251251
)
252252

253253
@staticmethod
254-
def from_yaml(yaml_config: dict | Path | str) -> Problem:
254+
def from_yaml(
255+
yaml_config: dict | Path | str, base_path: str | Path = None
256+
) -> Problem:
255257
"""
256258
Factory method to load model and tables as specified by YAML file.
257259
258260
Arguments:
259261
yaml_config: PEtab configuration as dictionary or YAML file name
262+
base_path: Base directory or URL to resolve relative paths
260263
"""
261264
if isinstance(yaml_config, Path):
262265
yaml_config = str(yaml_config)
263266

264-
get_path = lambda filename: filename # noqa: E731
265267
if isinstance(yaml_config, str):
266-
path_prefix = get_path_prefix(yaml_config)
268+
if base_path is None:
269+
base_path = get_path_prefix(yaml_config)
267270
yaml_config = yaml.load_yaml(yaml_config)
268-
get_path = lambda filename: f"{path_prefix}/{filename}" # noqa: E731
271+
272+
def get_path(filename):
273+
if base_path is None:
274+
return filename
275+
return f"{base_path}/{filename}"
269276

270277
if yaml.is_composite_problem(yaml_config):
271278
raise ValueError(

petab/v2/problem.py

+13-6
Original file line numberDiff line numberDiff line change
@@ -117,24 +117,31 @@ def __str__(self):
117117
)
118118

119119
@staticmethod
120-
def from_yaml(yaml_config: dict | Path | str) -> Problem:
120+
def from_yaml(
121+
yaml_config: dict | Path | str, base_path: str | Path = None
122+
) -> Problem:
121123
"""
122124
Factory method to load model and tables as specified by YAML file.
123125
124126
Arguments:
125127
yaml_config: PEtab configuration as dictionary or YAML file name
128+
base_path: Base directory or URL to resolve relative paths
126129
"""
127130
if isinstance(yaml_config, Path):
128131
yaml_config = str(yaml_config)
129132

130133
if isinstance(yaml_config, str):
131134
yaml_file = yaml_config
132-
path_prefix = get_path_prefix(yaml_file)
133-
yaml_config = yaml.load_yaml(yaml_config)
134-
get_path = lambda filename: f"{path_prefix}/{filename}" # noqa: E731
135+
if base_path is None:
136+
base_path = get_path_prefix(yaml_file)
137+
yaml_config = yaml.load_yaml(yaml_file)
135138
else:
136139
yaml_file = None
137-
get_path = lambda filename: filename # noqa: E731
140+
141+
def get_path(filename):
142+
if base_path is None:
143+
return filename
144+
return f"{base_path}/{filename}"
138145

139146
if yaml_config[FORMAT_VERSION] not in {"2.0.0"}:
140147
# If we got a path to a v1 yaml file, try to auto-upgrade
@@ -186,7 +193,7 @@ def from_yaml(yaml_config: dict | Path | str) -> Problem:
186193
else None
187194
)
188195

189-
if len(problem0[MODEL_FILES]) > 1:
196+
if len(problem0[MODEL_FILES] or []) > 1:
190197
# TODO https://github.com/PEtab-dev/libpetab-python/issues/6
191198
raise NotImplementedError(
192199
"Support for multiple models is not yet implemented."

tests/v1/test_petab.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -862,11 +862,16 @@ def test_problem_from_yaml_v1_multiple_files():
862862
observables_df, Path(tmpdir, f"observables{i}.tsv")
863863
)
864864

865-
petab_problem = petab.Problem.from_yaml(yaml_path)
865+
petab_problem1 = petab.Problem.from_yaml(yaml_path)
866866

867-
assert petab_problem.measurement_df.shape[0] == 2
868-
assert petab_problem.observable_df.shape[0] == 2
869-
assert petab_problem.condition_df.shape[0] == 2
867+
# test that we can load the problem from a dict with a custom base path
868+
yaml_config = petab.v1.load_yaml(yaml_path)
869+
petab_problem2 = petab.Problem.from_yaml(yaml_config, base_path=tmpdir)
870+
871+
for petab_problem in (petab_problem1, petab_problem2):
872+
assert petab_problem.measurement_df.shape[0] == 2
873+
assert petab_problem.observable_df.shape[0] == 2
874+
assert petab_problem.condition_df.shape[0] == 2
870875

871876

872877
def test_get_required_parameters_for_parameter_table(petab_problem):

tests/v2/test_problem.py

+80
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
1+
import tempfile
2+
from pathlib import Path
3+
4+
import pandas as pd
5+
6+
import petab.v2 as petab
17
from petab.v2 import Problem
8+
from petab.v2.C import (
9+
CONDITION_ID,
10+
MEASUREMENT,
11+
NOISE_FORMULA,
12+
OBSERVABLE_FORMULA,
13+
OBSERVABLE_ID,
14+
SIMULATION_CONDITION_ID,
15+
TIME,
16+
)
217

318

419
def test_load_remote():
@@ -25,3 +40,68 @@ def test_auto_upgrade():
2540
problem = Problem.from_yaml(yaml_url)
2641
# TODO check something specifically different in a v2 problem
2742
assert isinstance(problem, Problem)
43+
44+
45+
def test_problem_from_yaml_multiple_files():
46+
"""Test loading PEtab version 2 yaml with multiple condition / measurement
47+
/ observable files
48+
"""
49+
yaml_config = """
50+
format_version: 2.0.0
51+
parameter_file:
52+
problems:
53+
- condition_files: [conditions1.tsv, conditions2.tsv]
54+
measurement_files: [measurements1.tsv, measurements2.tsv]
55+
observable_files: [observables1.tsv, observables2.tsv]
56+
model_files:
57+
"""
58+
59+
with tempfile.TemporaryDirectory() as tmpdir:
60+
yaml_path = Path(tmpdir, "problem.yaml")
61+
with open(yaml_path, "w") as f:
62+
f.write(yaml_config)
63+
64+
for i in (1, 2):
65+
condition_df = pd.DataFrame(
66+
{
67+
CONDITION_ID: [f"condition{i}"],
68+
}
69+
)
70+
condition_df.set_index([CONDITION_ID], inplace=True)
71+
petab.write_condition_df(
72+
condition_df, Path(tmpdir, f"conditions{i}.tsv")
73+
)
74+
75+
measurement_df = pd.DataFrame(
76+
{
77+
SIMULATION_CONDITION_ID: [f"condition{i}"],
78+
OBSERVABLE_ID: [f"observable{i}"],
79+
TIME: [i],
80+
MEASUREMENT: [1],
81+
}
82+
)
83+
petab.write_measurement_df(
84+
measurement_df, Path(tmpdir, f"measurements{i}.tsv")
85+
)
86+
87+
observables_df = pd.DataFrame(
88+
{
89+
OBSERVABLE_ID: [f"observable{i}"],
90+
OBSERVABLE_FORMULA: [1],
91+
NOISE_FORMULA: [1],
92+
}
93+
)
94+
petab.write_observable_df(
95+
observables_df, Path(tmpdir, f"observables{i}.tsv")
96+
)
97+
98+
petab_problem1 = petab.Problem.from_yaml(yaml_path)
99+
100+
# test that we can load the problem from a dict with a custom base path
101+
yaml_config = petab.load_yaml(yaml_path)
102+
petab_problem2 = petab.Problem.from_yaml(yaml_config, base_path=tmpdir)
103+
104+
for petab_problem in (petab_problem1, petab_problem2):
105+
assert petab_problem.measurement_df.shape[0] == 2
106+
assert petab_problem.observable_df.shape[0] == 2
107+
assert petab_problem.condition_df.shape[0] == 2

0 commit comments

Comments
 (0)